studiou-wc-product-cat-manage.php 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574
  1. <?php
  2. /**
  3. * Plugin Name: QDR - Studiou WC Export/Import Product Categories
  4. * Plugin URI: https://www.quadarax.com/plugins/studiou-wc-product-cat-manage
  5. * Description: Allows batch import and export WooCommerce product categories and products
  6. * Version: 1.6.0
  7. * Requires at least: 6.8.1
  8. * Requires PHP: 8.2
  9. * Author: Dalibor Votruba
  10. * Author URI: https://www.quadarax.com
  11. * License: GPL v2 or later
  12. * License URI: https://www.gnu.org/licenses/gpl-2.0.html
  13. * Text Domain: studiou-wc-product-cat-manage
  14. * Domain Path: /languages
  15. * WC requires at least: 9.0
  16. * WC tested up to: 9.8
  17. * Woo: 12345:342928dfsfhsf8429842374wdf4234sfd
  18. */
  19. // If this file is called directly, abort.
  20. if (!defined('WPINC')) {
  21. die;
  22. }
  23. // Define plugin constants
  24. if (!defined('STUDIOU_WCPCM_VERSION')) define('STUDIOU_WCPCM_VERSION', '1.6.0');
  25. if (!defined('STUDIOU_WCPCM_PLUGIN_DIR')) define('STUDIOU_WCPCM_PLUGIN_DIR', plugin_dir_path(__FILE__));
  26. if (!defined('STUDIOU_WCPCM_PLUGIN_URL')) define('STUDIOU_WCPCM_PLUGIN_URL', plugin_dir_url(__FILE__));
  27. if (!defined('STUDIOU_WCPCM_PLUGIN_BASENAME')) define('STUDIOU_WCPCM_PLUGIN_BASENAME', plugin_basename(__FILE__));
  28. /**
  29. * Main plugin class
  30. */
  31. class Studiou_WC_Product_Cat_Manage {
  32. /**
  33. * @var Studiou_WC_Product_Cat_Manage_Export
  34. */
  35. private $exporter;
  36. /**
  37. * @var Studiou_WC_Product_Cat_Manage_Import
  38. */
  39. private $importer;
  40. /**
  41. * @var Studiou_WC_Product_Cat_Manage_DB
  42. */
  43. private $db;
  44. /**
  45. * @var Studiou_WC_Product_Cat_Manage_Manipulator
  46. */
  47. private $manipulator;
  48. /**
  49. * @var Studiou_WC_Product_Manage_Product_DB
  50. */
  51. private $product_db;
  52. /**
  53. * @var Studiou_WC_Product_Manage_Product_Import
  54. */
  55. private $product_importer;
  56. /**
  57. * @var Studiou_WC_Product_Manage_Product_Export
  58. */
  59. private $product_exporter;
  60. /**
  61. * Initialize the plugin
  62. */
  63. public function __construct() {
  64. // Load dependencies
  65. $this->load_dependencies();
  66. // Check required plugins
  67. add_action('admin_init', array($this, 'check_required_plugins'));
  68. // Add admin menu items
  69. add_action('admin_menu', array($this, 'add_admin_menu'), 99);
  70. // Register admin assets
  71. add_action('admin_enqueue_scripts', array($this, 'register_admin_assets'));
  72. // Initialize components
  73. $this->init_components();
  74. // Declare WooCommerce HPOS compatibility
  75. add_action('before_woocommerce_init', array($this, 'declare_hpos_compatibility'));
  76. }
  77. /**
  78. * Declare compatibility with WooCommerce HPOS (High-Performance Order Storage)
  79. */
  80. public function declare_hpos_compatibility() {
  81. if (class_exists('Automattic\WooCommerce\Utilities\FeaturesUtil')) {
  82. \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility(
  83. 'custom_order_tables',
  84. __FILE__,
  85. true
  86. );
  87. }
  88. }
  89. /**
  90. * Load required dependencies
  91. */
  92. private function load_dependencies() {
  93. require_once STUDIOU_WCPCM_PLUGIN_DIR . 'includes/class-studiou-wc-product-cat-manage-db.php';
  94. require_once STUDIOU_WCPCM_PLUGIN_DIR . 'includes/class-studiou-wc-product-cat-manage-export.php';
  95. require_once STUDIOU_WCPCM_PLUGIN_DIR . 'includes/class-studiou-wc-product-cat-manage-import.php';
  96. require_once STUDIOU_WCPCM_PLUGIN_DIR . 'includes/class-studiou-wc-product-cat-manage-manipulator.php';
  97. require_once STUDIOU_WCPCM_PLUGIN_DIR . 'includes/class-studiou-wc-product-manage-product-db.php';
  98. require_once STUDIOU_WCPCM_PLUGIN_DIR . 'includes/class-studiou-wc-product-manage-product-import.php';
  99. require_once STUDIOU_WCPCM_PLUGIN_DIR . 'includes/class-studiou-wc-product-manage-product-export.php';
  100. }
  101. /**
  102. * Initialize components
  103. */
  104. private function init_components() {
  105. $this->db = new Studiou_WC_Product_Cat_Manage_DB();
  106. $this->exporter = new Studiou_WC_Product_Cat_Manage_Export($this->db);
  107. $this->importer = new Studiou_WC_Product_Cat_Manage_Import($this->db);
  108. $this->manipulator = new Studiou_WC_Product_Cat_Manage_Manipulator($this->db);
  109. $this->product_db = new Studiou_WC_Product_Manage_Product_DB();
  110. $this->product_importer = new Studiou_WC_Product_Manage_Product_Import($this->product_db);
  111. $this->product_exporter = new Studiou_WC_Product_Manage_Product_Export($this->product_db);
  112. // Register AJAX handlers
  113. add_action('wp_ajax_studiou_wcpcm_product_import_parse', array($this, 'handle_product_import_parse'));
  114. add_action('wp_ajax_studiou_wcpcm_product_import_batch', array($this, 'handle_product_import_batch'));
  115. add_action('wp_ajax_studiou_wcpcm_product_export', array($this, 'handle_product_export'));
  116. }
  117. /**
  118. * Check if required plugins are active
  119. */
  120. public function check_required_plugins() {
  121. if (!is_admin() || isset($_GET['activate-multi'])) {
  122. return;
  123. }
  124. // Check for WooCommerce
  125. if (!class_exists('WooCommerce') || !defined('WC_VERSION') || version_compare(WC_VERSION, '9.8', '<')) {
  126. add_action('admin_notices', array($this, 'woocommerce_missing_notice'));
  127. deactivate_plugins(STUDIOU_WCPCM_PLUGIN_BASENAME);
  128. if (isset($_GET['activate'])) {
  129. unset($_GET['activate']);
  130. }
  131. }
  132. }
  133. /**
  134. * WooCommerce missing notice
  135. */
  136. public function woocommerce_missing_notice() {
  137. ?>
  138. <div class="error">
  139. <p><?php echo sprintf(
  140. __('QDR - Studiou WC Export/Import Product Categories requires WooCommerce version 9.8 or higher. Please %sinstall or update WooCommerce%s.', 'studiou-wc-product-cat-manage'),
  141. '<a href="' . admin_url('plugin-install.php?s=woocommerce&tab=search&type=term') . '">',
  142. '</a>'
  143. ); ?></p>
  144. </div>
  145. <?php
  146. }
  147. /**
  148. * Add admin menu items
  149. */
  150. public function add_admin_menu() {
  151. // Make sure WooCommerce menu exists
  152. if (class_exists('WooCommerce')) {
  153. add_submenu_page(
  154. 'edit.php?post_type=product',
  155. __('Import Product Categories', 'studiou-wc-product-cat-manage'),
  156. __('Import Product Categories', 'studiou-wc-product-cat-manage'),
  157. 'manage_woocommerce',
  158. 'studiou-import-product-categories',
  159. array($this, 'render_import_page')
  160. );
  161. add_submenu_page(
  162. 'edit.php?post_type=product',
  163. __('Export Product Categories', 'studiou-wc-product-cat-manage'),
  164. __('Export Product Categories', 'studiou-wc-product-cat-manage'),
  165. 'manage_woocommerce',
  166. 'studiou-export-product-categories',
  167. array($this, 'render_export_page')
  168. );
  169. add_submenu_page(
  170. 'edit.php?post_type=product',
  171. __('Category Manipulations', 'studiou-wc-product-cat-manage'),
  172. __('Category Manipulations', 'studiou-wc-product-cat-manage'),
  173. 'manage_woocommerce',
  174. 'studiou-category-manipulations',
  175. array($this, 'render_manipulations_page')
  176. );
  177. add_submenu_page(
  178. 'edit.php?post_type=product',
  179. __('Product Import', 'studiou-wc-product-cat-manage'),
  180. __('Product Import', 'studiou-wc-product-cat-manage'),
  181. 'manage_woocommerce',
  182. 'studiou-product-import',
  183. array($this, 'render_product_import_page')
  184. );
  185. add_submenu_page(
  186. 'edit.php?post_type=product',
  187. __('Product Export', 'studiou-wc-product-cat-manage'),
  188. __('Product Export', 'studiou-wc-product-cat-manage'),
  189. 'manage_woocommerce',
  190. 'studiou-product-export',
  191. array($this, 'render_product_export_page')
  192. );
  193. }
  194. }
  195. /**
  196. * Register admin assets
  197. */
  198. public function register_admin_assets($hook) {
  199. // Debug: Log hook being checked
  200. if (defined('WP_DEBUG') && WP_DEBUG) {
  201. error_log('STUDIOU WC: Checking admin assets for hook: ' . $hook);
  202. }
  203. if ('product_page_studiou-import-product-categories' === $hook ||
  204. 'product_page_studiou-export-product-categories' === $hook ||
  205. 'product_page_studiou-category-manipulations' === $hook ||
  206. 'product_page_studiou-product-import' === $hook ||
  207. 'product_page_studiou-product-export' === $hook) {
  208. if (defined('WP_DEBUG') && WP_DEBUG) {
  209. error_log('STUDIOU WC: Loading admin assets for hook: ' . $hook);
  210. }
  211. wp_enqueue_style(
  212. 'studiou-wcpcm-admin',
  213. $this->get_cache_busted_asset_url('assets/css/admin', 'css'),
  214. array(),
  215. STUDIOU_WCPCM_VERSION
  216. );
  217. wp_enqueue_script(
  218. 'studiou-wcpcm-admin',
  219. $this->get_cache_busted_asset_url('assets/js/admin', 'js'),
  220. array('jquery'),
  221. STUDIOU_WCPCM_VERSION,
  222. true
  223. );
  224. wp_localize_script('studiou-wcpcm-admin', 'studiouWcpcm', array(
  225. 'ajaxUrl' => admin_url('admin-ajax.php'),
  226. 'nonce' => wp_create_nonce('studiou-wcpcm-nonce'),
  227. 'i18n' => array(
  228. 'importSuccess' => __('Import successful', 'studiou-wc-product-cat-manage'),
  229. 'importError' => __('Import error', 'studiou-wc-product-cat-manage'),
  230. 'exportSuccess' => __('Export successful', 'studiou-wc-product-cat-manage'),
  231. 'exportError' => __('Export error', 'studiou-wc-product-cat-manage'),
  232. 'moveSuccess' => __('Move operation successful', 'studiou-wc-product-cat-manage'),
  233. 'moveError' => __('Move operation error', 'studiou-wc-product-cat-manage'),
  234. 'selectDifferentCategories' => __('Please select different source and target categories', 'studiou-wc-product-cat-manage'),
  235. 'batchDescriptionSuccess' => __('Batch description operation successful', 'studiou-wc-product-cat-manage'),
  236. 'batchDescriptionError' => __('Batch description operation error', 'studiou-wc-product-cat-manage'),
  237. 'deleteSuccess' => __('Delete operation successful', 'studiou-wc-product-cat-manage'),
  238. 'deleteError' => __('Delete operation error', 'studiou-wc-product-cat-manage'),
  239. 'confirmDelete' => __('Are you sure you want to permanently delete ALL products in the selected categories? This action cannot be undone!', 'studiou-wc-product-cat-manage'),
  240. 'selectAtLeastOneCategory' => __('Please select at least one category', 'studiou-wc-product-cat-manage'),
  241. 'noProductsFound' => __('No products found in selected categories', 'studiou-wc-product-cat-manage'),
  242. 'confirmClearMedia' => __('Are you sure you want to permanently delete ALL media files in the selected media categories? This action cannot be undone!', 'studiou-wc-product-cat-manage'),
  243. 'clearMediaSuccess' => __('Clear media operation successful', 'studiou-wc-product-cat-manage'),
  244. 'clearMediaError' => __('Clear media operation error', 'studiou-wc-product-cat-manage'),
  245. 'noMediaFound' => __('No media files found in selected categories', 'studiou-wc-product-cat-manage'),
  246. 'confirmRemoveUnassigned' => __('Are you sure you want to permanently delete ALL unused uncategorized media files? This action cannot be undone!', 'studiou-wc-product-cat-manage'),
  247. 'removeUnassignedSuccess' => __('Remove unassigned media operation successful', 'studiou-wc-product-cat-manage'),
  248. 'removeUnassignedError' => __('Remove unassigned media operation error', 'studiou-wc-product-cat-manage'),
  249. 'noUnassignedMedia' => __('No unused uncategorized media files found', 'studiou-wc-product-cat-manage'),
  250. 'upvpSelectAttribute' => __('-- Select attribute --', 'studiou-wc-product-cat-manage'),
  251. 'upvpSelectValue' => __('-- Select value --', 'studiou-wc-product-cat-manage'),
  252. 'upvpInvalidPrice' => __('Please enter a valid new price', 'studiou-wc-product-cat-manage'),
  253. 'upvpLoadingAttributes' => __('Loading attributes...', 'studiou-wc-product-cat-manage'),
  254. 'upvpLoadingValues' => __('Loading values...', 'studiou-wc-product-cat-manage'),
  255. 'upvpDryRunComplete' => __('Dry run complete: %d variations would be updated to %s.', 'studiou-wc-product-cat-manage'),
  256. 'upvpApplySuccess' => __('Variant prices updated successfully', 'studiou-wc-product-cat-manage'),
  257. 'upvpApplyError' => __('Variant prices update error', 'studiou-wc-product-cat-manage'),
  258. 'upvpNoMatches' => __('No variations match the current filter.', 'studiou-wc-product-cat-manage'),
  259. 'upvpConfirmApply' => __('Are you sure you want to update %d variations?', 'studiou-wc-product-cat-manage'),
  260. 'tabLoading' => __('Loading...', 'studiou-wc-product-cat-manage'),
  261. 'tabLoadError' => __('Failed to load tab', 'studiou-wc-product-cat-manage'),
  262. )
  263. ));
  264. if (defined('WP_DEBUG') && WP_DEBUG) {
  265. error_log('STUDIOU WC: Admin assets loaded successfully');
  266. }
  267. }
  268. }
  269. /**
  270. * Build a cache-busting URL for a plugin asset.
  271. *
  272. * studiou.cz (and similar host optimisers) strip the `?ver=` query string
  273. * from static assets, so a version bump alone does not bust the browser
  274. * cache. To work around that, this serves the asset under a filename that
  275. * embeds the plugin version, e.g. assets/js/admin-1.6.0.js. The versioned
  276. * copy is generated automatically from the source file and refreshed
  277. * whenever the source changes, so no manual file renaming is needed.
  278. *
  279. * Falls back to the plain source URL if the copy cannot be written.
  280. *
  281. * @param string $rel_no_ext Asset path relative to the plugin dir, without
  282. * extension, e.g. 'assets/js/admin'.
  283. * @param string $ext File extension without the dot, e.g. 'js'.
  284. * @return string Absolute URL to the asset.
  285. */
  286. private function get_cache_busted_asset_url($rel_no_ext, $ext) {
  287. $source_rel = $rel_no_ext . '.' . $ext;
  288. $source_abs = STUDIOU_WCPCM_PLUGIN_DIR . $source_rel;
  289. // Source missing - nothing we can do, return the plain URL.
  290. if (!file_exists($source_abs)) {
  291. return STUDIOU_WCPCM_PLUGIN_URL . $source_rel;
  292. }
  293. $versioned_rel = $rel_no_ext . '-' . STUDIOU_WCPCM_VERSION . '.' . $ext;
  294. $versioned_abs = STUDIOU_WCPCM_PLUGIN_DIR . $versioned_rel;
  295. // Regenerate the versioned copy if it is missing or out of date.
  296. if (!file_exists($versioned_abs) || filemtime($source_abs) > filemtime($versioned_abs)) {
  297. // Remove stale versioned copies of this asset.
  298. $old_files = glob(STUDIOU_WCPCM_PLUGIN_DIR . $rel_no_ext . '-*.' . $ext);
  299. if (is_array($old_files)) {
  300. foreach ($old_files as $old_file) {
  301. @unlink($old_file);
  302. }
  303. }
  304. // Copy failed (e.g. read-only filesystem) - fall back to plain URL.
  305. if (!@copy($source_abs, $versioned_abs)) {
  306. return STUDIOU_WCPCM_PLUGIN_URL . $source_rel;
  307. }
  308. }
  309. return STUDIOU_WCPCM_PLUGIN_URL . $versioned_rel;
  310. }
  311. /**
  312. * Render import page
  313. */
  314. public function render_import_page() {
  315. include STUDIOU_WCPCM_PLUGIN_DIR . 'views/import.php';
  316. }
  317. /**
  318. * Render export page
  319. */
  320. public function render_export_page() {
  321. include STUDIOU_WCPCM_PLUGIN_DIR . 'views/export.php';
  322. }
  323. /**
  324. * Render manipulations page
  325. */
  326. public function render_manipulations_page() {
  327. include STUDIOU_WCPCM_PLUGIN_DIR . 'views/manipulations.php';
  328. }
  329. /**
  330. * Render product import page
  331. */
  332. public function render_product_import_page() {
  333. include STUDIOU_WCPCM_PLUGIN_DIR . 'views/product-import.php';
  334. }
  335. /**
  336. * Render product export page
  337. */
  338. public function render_product_export_page() {
  339. include STUDIOU_WCPCM_PLUGIN_DIR . 'views/product-export.php';
  340. }
  341. /**
  342. * Handle product import parse AJAX request (step 1: parse CSV)
  343. */
  344. public function handle_product_import_parse() {
  345. // Clean any output buffers
  346. while (ob_get_level()) {
  347. ob_end_clean();
  348. }
  349. // Verify nonce
  350. if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'studiou-wcpcm-nonce')) {
  351. wp_send_json_error(array('message' => __('Security check failed', 'studiou-wc-product-cat-manage')));
  352. return;
  353. }
  354. // Check permissions
  355. if (!current_user_can('manage_woocommerce')) {
  356. wp_send_json_error(array('message' => __('Insufficient permissions', 'studiou-wc-product-cat-manage')));
  357. return;
  358. }
  359. // Check if file was uploaded
  360. if (!isset($_FILES['import_file']) || $_FILES['import_file']['error'] !== UPLOAD_ERR_OK) {
  361. wp_send_json_error(array('message' => __('No file uploaded or upload error', 'studiou-wc-product-cat-manage')));
  362. return;
  363. }
  364. try {
  365. // Parse CSV and store in transient
  366. $result = $this->product_importer->parse_csv_for_batch($_FILES['import_file']['tmp_name']);
  367. if (isset($result['error'])) {
  368. wp_send_json_error(array('message' => $result['error']));
  369. return;
  370. }
  371. wp_send_json_success(array(
  372. 'transient_key' => $result['transient_key'],
  373. 'total' => $result['total']
  374. ));
  375. } catch (Exception $e) {
  376. if (defined('WP_DEBUG') && WP_DEBUG) {
  377. error_log('STUDIOU WC Product Import Parse Error: ' . $e->getMessage());
  378. }
  379. wp_send_json_error(array('message' => $e->getMessage()));
  380. }
  381. }
  382. /**
  383. * Handle product import batch AJAX request (step 2: process batch)
  384. */
  385. public function handle_product_import_batch() {
  386. // Clean any output buffers
  387. while (ob_get_level()) {
  388. ob_end_clean();
  389. }
  390. // Verify nonce
  391. if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'studiou-wcpcm-nonce')) {
  392. wp_send_json_error(array('message' => __('Security check failed', 'studiou-wc-product-cat-manage')));
  393. return;
  394. }
  395. // Check permissions
  396. if (!current_user_can('manage_woocommerce')) {
  397. wp_send_json_error(array('message' => __('Insufficient permissions', 'studiou-wc-product-cat-manage')));
  398. return;
  399. }
  400. // Get parameters
  401. $transient_key = isset($_POST['transient_key']) ? sanitize_text_field($_POST['transient_key']) : '';
  402. $offset = isset($_POST['offset']) ? intval($_POST['offset']) : 0;
  403. if (empty($transient_key)) {
  404. wp_send_json_error(array('message' => __('Invalid request', 'studiou-wc-product-cat-manage')));
  405. return;
  406. }
  407. try {
  408. // Process batch
  409. $result = $this->product_importer->process_batch($transient_key, $offset, 5);
  410. if (isset($result['error'])) {
  411. wp_send_json_error(array('message' => $result['error']));
  412. return;
  413. }
  414. // Generate failed CSV if this is the last batch and there are failed items
  415. $failed_csv = '';
  416. if ($result['processed'] >= $result['total'] && !empty($result['failed_items'])) {
  417. $failed_csv = $this->product_importer->generate_failed_csv($result['failed_items']);
  418. }
  419. wp_send_json_success(array(
  420. 'success' => $result['success'],
  421. 'failed' => $result['failed'],
  422. 'skipped' => $result['skipped'],
  423. 'messages' => $result['messages'],
  424. 'processed' => $result['processed'],
  425. 'total' => $result['total'],
  426. 'failed_csv' => $failed_csv
  427. ));
  428. } catch (Exception $e) {
  429. if (defined('WP_DEBUG') && WP_DEBUG) {
  430. error_log('STUDIOU WC Product Import Batch Error: ' . $e->getMessage());
  431. }
  432. wp_send_json_error(array('message' => $e->getMessage()));
  433. }
  434. }
  435. /**
  436. * Handle product export AJAX request
  437. */
  438. public function handle_product_export() {
  439. // Clean any output buffers
  440. while (ob_get_level()) {
  441. ob_end_clean();
  442. }
  443. // Verify nonce
  444. if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'studiou-wcpcm-nonce')) {
  445. wp_send_json_error(array('message' => __('Security check failed', 'studiou-wc-product-cat-manage')));
  446. return;
  447. }
  448. // Check permissions
  449. if (!current_user_can('manage_woocommerce')) {
  450. wp_send_json_error(array('message' => __('Insufficient permissions', 'studiou-wc-product-cat-manage')));
  451. return;
  452. }
  453. // Get selected categories
  454. $category_ids = isset($_POST['export_categories']) ? array_map('intval', $_POST['export_categories']) : array();
  455. if (empty($category_ids)) {
  456. wp_send_json_error(array('message' => __('No categories selected', 'studiou-wc-product-cat-manage')));
  457. return;
  458. }
  459. try {
  460. // Generate export file
  461. $result = $this->product_exporter->generate_export_file($category_ids);
  462. if ($result['success']) {
  463. wp_send_json_success(array(
  464. 'message' => $result['message'],
  465. 'download_url' => $result['download_url']
  466. ));
  467. } else {
  468. wp_send_json_error(array('message' => $result['message']));
  469. }
  470. } catch (Exception $e) {
  471. if (defined('WP_DEBUG') && WP_DEBUG) {
  472. error_log('STUDIOU WC Product Export Error: ' . $e->getMessage());
  473. }
  474. wp_send_json_error(array('message' => $e->getMessage()));
  475. }
  476. }
  477. }
  478. /**
  479. * Begin execution of the plugin
  480. */
  481. function run_studiou_wc_product_cat_manage() {
  482. // Only initialize once
  483. static $initialized = false;
  484. if ($initialized) {
  485. return;
  486. }
  487. $initialized = true;
  488. // Load text domain first
  489. load_plugin_textdomain(
  490. 'studiou-wc-product-cat-manage',
  491. false,
  492. dirname(plugin_basename(__FILE__)) . '/languages/'
  493. );
  494. // Create instance
  495. new Studiou_WC_Product_Cat_Manage();
  496. }
  497. // Run the plugin after plugins are loaded to ensure WooCommerce is available
  498. add_action('plugins_loaded', 'run_studiou_wc_product_cat_manage');