load_dependencies(); add_action('admin_init', array($this, 'check_required_plugins')); add_action('admin_menu', array($this, 'add_admin_menu'), 99); add_action('admin_enqueue_scripts', array($this, 'register_admin_assets')); add_action('wp_enqueue_scripts', array($this, 'register_frontend_assets')); $this->init_components(); add_action('before_woocommerce_init', array($this, 'declare_hpos_compatibility')); // Override WC core strings with FPP-specific wording (e.g. cart "Estimated total" // → "Součet" in cs_CZ). Translation lives in our .po so it stays locale-aware. add_filter('gettext_woocommerce', array($this, 'override_wc_strings'), 10, 2); } /** * Reword selected WooCommerce core strings via our own textdomain so translations * stay centralized in languages/studiou-wc-free-photo-product-*.po. */ public function override_wc_strings($translation, $text) { if ($text === 'Estimated total') { return __('Total', 'studiou-wc-free-photo-product'); } return $translation; } private function load_dependencies() { require_once STUDIOU_WCFPP_PLUGIN_DIR . 'includes/class-studiou-wc-fpp-db.php'; require_once STUDIOU_WCFPP_PLUGIN_DIR . 'includes/class-studiou-wc-fpp-product.php'; require_once STUDIOU_WCFPP_PLUGIN_DIR . 'includes/class-studiou-wc-fpp-upload.php'; require_once STUDIOU_WCFPP_PLUGIN_DIR . 'includes/class-studiou-wc-fpp-shortcode.php'; require_once STUDIOU_WCFPP_PLUGIN_DIR . 'includes/class-studiou-wc-fpp-single-product.php'; require_once STUDIOU_WCFPP_PLUGIN_DIR . 'includes/class-studiou-wc-fpp-cart.php'; require_once STUDIOU_WCFPP_PLUGIN_DIR . 'includes/class-studiou-wc-fpp-admin.php'; require_once STUDIOU_WCFPP_PLUGIN_DIR . 'includes/class-studiou-wc-fpp-pricing.php'; } private function init_components() { $this->db = new Studiou_WC_FPP_DB(); $this->maybe_upgrade_db(); $this->product = new Studiou_WC_FPP_Product(); $this->upload = new Studiou_WC_FPP_Upload($this->db); $this->shortcode = new Studiou_WC_FPP_Shortcode(); $this->single_product = new Studiou_WC_FPP_Single_Product($this->db); $this->cart = new Studiou_WC_FPP_Cart($this->db); $this->admin = new Studiou_WC_FPP_Admin($this->db); $this->pricing = new Studiou_WC_FPP_Pricing(); } /** * Run dbDelta when the installed schema is older than the current plugin version. * dbDelta handles adding the batch_token column and its KEY on existing installs. */ private function maybe_upgrade_db() { $installed = get_option('studiou_wcfpp_db_version', ''); if (version_compare((string) $installed, STUDIOU_WCFPP_VERSION, '<')) { $this->db->create_tables(); } } public function check_required_plugins() { if (!class_exists('WooCommerce')) { add_action('admin_notices', function () { echo '

'; echo esc_html__('QDR - Studiou WC Free Photo Product requires WooCommerce to be installed and activated.', 'studiou-wc-free-photo-product'); echo '

'; }); } if (!self::is_pvtfw_active()) { add_action('admin_notices', function () { echo '

'; echo esc_html__('QDR - Studiou WC Free Photo Product requires the "Product Variant Table for WooCommerce" plugin to be installed and activated.', 'studiou-wc-free-photo-product'); echo '

'; }); } } /** * Detects whether the Product Variant Table for WooCommerce (pvtfw) plugin is active, * regardless of its main file name, by matching the active-plugins list on the folder slug. */ public static function is_pvtfw_active() { $slug = 'product-variant-table-for-woocommerce/'; $active = (array) get_option('active_plugins', array()); foreach ($active as $plugin) { if (strpos($plugin, $slug) === 0) { return true; } } if (is_multisite()) { $network = (array) get_site_option('active_sitewide_plugins', array()); foreach (array_keys($network) as $plugin) { if (strpos($plugin, $slug) === 0) { return true; } } } return false; } public function declare_hpos_compatibility() { if (class_exists('Automattic\WooCommerce\Utilities\FeaturesUtil')) { \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility( 'custom_order_tables', __FILE__, true ); } } public function add_admin_menu() { if (class_exists('WooCommerce')) { add_submenu_page( 'edit.php?post_type=product', __('Free Photo Files', 'studiou-wc-free-photo-product'), __('Free Photo Files', 'studiou-wc-free-photo-product'), 'manage_woocommerce', 'studiou-fpp-summary', array($this->admin, 'render_summary_page') ); } } public function register_admin_assets($hook) { // Product edit page $screen = get_current_screen(); if ($screen && ($screen->id === 'product' || $screen->post_type === 'product')) { wp_enqueue_style( 'studiou-wcfpp-admin', STUDIOU_WCFPP_PLUGIN_URL . 'assets/css/admin.css', array(), STUDIOU_WCFPP_VERSION ); wp_enqueue_script( 'studiou-wcfpp-admin', STUDIOU_WCFPP_PLUGIN_URL . 'assets/js/admin.js', array('jquery'), STUDIOU_WCFPP_VERSION, true ); wp_localize_script('studiou-wcfpp-admin', 'studiouWcfpp', array( 'ajaxUrl' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('studiou-wcfpp-nonce'), 'i18n' => array( 'confirmDelete' => __('Are you sure you want to delete the selected files?', 'studiou-wc-free-photo-product'), 'confirmDeleteSingle' => __('Are you sure you want to delete this file?', 'studiou-wc-free-photo-product'), 'confirmDownloadZip' => __('Download selected files as ZIP archive?', 'studiou-wc-free-photo-product'), 'confirmStatusChange' => __('Are you sure you want to change the status of the selected files?', 'studiou-wc-free-photo-product'), 'noFilesSelected' => __('No files selected.', 'studiou-wc-free-photo-product'), 'statusUpdated' => __('Status updated.', 'studiou-wc-free-photo-product'), 'error' => __('An error occurred.', 'studiou-wc-free-photo-product'), 'generatingZip' => __('Generating ZIP archive...', 'studiou-wc-free-photo-product'), 'generatingCsv' => __('Generating CSV file...', 'studiou-wc-free-photo-product'), 'deleting' => __('Deleting...', 'studiou-wc-free-photo-product'), ), )); } // Admin summary page if ('product_page_studiou-fpp-summary' === $hook) { wp_enqueue_style( 'studiou-wcfpp-admin', STUDIOU_WCFPP_PLUGIN_URL . 'assets/css/admin.css', array(), STUDIOU_WCFPP_VERSION ); wp_enqueue_script( 'studiou-wcfpp-admin', STUDIOU_WCFPP_PLUGIN_URL . 'assets/js/admin.js', array('jquery'), STUDIOU_WCFPP_VERSION, true ); wp_localize_script('studiou-wcfpp-admin', 'studiouWcfpp', array( 'ajaxUrl' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('studiou-wcfpp-nonce'), 'i18n' => array( 'confirmDelete' => __('Are you sure you want to delete the selected files?', 'studiou-wc-free-photo-product'), 'confirmDeleteSingle' => __('Are you sure you want to delete this file?', 'studiou-wc-free-photo-product'), 'confirmDownloadZip' => __('Download selected files as ZIP archive?', 'studiou-wc-free-photo-product'), 'confirmStatusChange' => __('Are you sure you want to change the status of the selected files?', 'studiou-wc-free-photo-product'), 'noFilesSelected' => __('No files selected.', 'studiou-wc-free-photo-product'), 'statusUpdated' => __('Status updated.', 'studiou-wc-free-photo-product'), 'error' => __('An error occurred.', 'studiou-wc-free-photo-product'), 'generatingZip' => __('Generating ZIP archive...', 'studiou-wc-free-photo-product'), 'generatingCsv' => __('Generating CSV file...', 'studiou-wc-free-photo-product'), 'deleting' => __('Deleting...', 'studiou-wc-free-photo-product'), ), )); } } public function register_frontend_assets() { // WC block cart ("Estimated total" etc.) is React-rendered and pulls its strings // from wp.i18n in the browser — the PHP gettext_woocommerce filter never fires for // those strings. Patch the translation on the JS side. // // IMPORTANT: WC's wc-cart-checkout-base-js-translations inline bundle calls // wp.i18n.setLocaleData(cartLocaleData, 'woocommerce') AFTER our override, which // merges the WC-bundled "Estimated total": ["Odhadovaný součet"] back on top of // ours. So a single one-shot override on wp-i18n is clobbered. // // We install a permanent override by: // 1) re-applying after every future setLocaleData call via wp.hooks (if available), and // 2) attaching another override as a `before` inline script on wc-cart-checkout-base, // which runs AFTER the translations bundle has merged its own data in. $total_txt = wp_json_encode(__('Total', 'studiou-wc-free-photo-product')); $override_js = "( function() {"; $override_js .= " if ( ! window.wp || ! wp.i18n ) return;"; $override_js .= " var domain = 'woocommerce';"; $override_js .= " var txt = " . $total_txt . ";"; $override_js .= " function apply() { try {"; $override_js .= " wp.i18n.setLocaleData( { 'Estimated total': [ txt ], 'Cart totals': [ txt ] }, domain );"; $override_js .= " } catch (e) {} }"; $override_js .= " apply();"; // Re-apply on every subsequent wp.i18n data change (covers WC's late bundle load). $override_js .= " if ( wp.hooks && wp.hooks.addAction ) {"; $override_js .= " wp.hooks.addAction( 'i18n.gettext_with_context_woocommerce', 'studiou-fpp/total-override', apply );"; $override_js .= " wp.hooks.addAction( 'i18n.gettext_woocommerce', 'studiou-fpp/total-override', apply );"; $override_js .= " }"; // Also re-apply after DOMContentLoaded as a last-line safety net. $override_js .= " if ( document.readyState === 'loading' ) {"; $override_js .= " document.addEventListener( 'DOMContentLoaded', apply );"; $override_js .= " }"; $override_js .= " } )();"; wp_add_inline_script('wp-i18n', $override_js, 'after'); // The critical bit: override AFTER WC's cart-checkout-base translations bundle runs. wp_add_inline_script('wc-cart-checkout-base', $override_js, 'after'); wp_add_inline_script('wc-cart-block-frontend', $override_js, 'after'); wp_register_style( 'studiou-wcfpp-frontend', STUDIOU_WCFPP_PLUGIN_URL . 'assets/css/frontend.css', array(), STUDIOU_WCFPP_VERSION ); wp_register_script( 'studiou-wcfpp-frontend', STUDIOU_WCFPP_PLUGIN_URL . 'assets/js/frontend.js', array('jquery'), STUDIOU_WCFPP_VERSION, true ); wp_localize_script('studiou-wcfpp-frontend', 'studiouWcfppFront', array( 'ajaxUrl' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('studiou-wcfpp-front-nonce'), 'chunkSize' => STUDIOU_WCFPP_CHUNK_SIZE, 'cartUrl' => wc_get_cart_url(), 'currency' => array( // WooCommerce returns these with HTML entities (Kč for Kč,   // embedded in priceFormat). Decoded here so the plain-text price formatter // that fills