class-studiou-wc-product-cat-manage-manipulator.php 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857
  1. <?php
  2. /**
  3. * Category Manipulator class
  4. *
  5. * Handles manipulation operations for product categories
  6. *
  7. * @since 1.0.0
  8. * @package Studiou_WC_Product_Cat_Manage
  9. * @subpackage Studiou_WC_Product_Cat_Manage/includes
  10. */
  11. // If this file is called directly, abort.
  12. if (!defined('WPINC')) {
  13. die;
  14. }
  15. class Studiou_WC_Product_Cat_Manage_Manipulator {
  16. /**
  17. * Database handler
  18. *
  19. * @var Studiou_WC_Product_Cat_Manage_DB
  20. */
  21. private $db;
  22. /**
  23. * Constructor
  24. *
  25. * @param Studiou_WC_Product_Cat_Manage_DB $db Database handler
  26. */
  27. public function __construct($db) {
  28. $this->db = $db;
  29. // Register AJAX handlers
  30. add_action('wp_ajax_studiou_wcpcm_move_products', array($this, 'handle_move_products_ajax'));
  31. add_action('wp_ajax_studiou_wcpcm_batch_description', array($this, 'handle_batch_description_ajax'));
  32. add_action('wp_ajax_studiou_wcpcm_delete_products', array($this, 'handle_delete_products_ajax'));
  33. add_action('wp_ajax_studiou_wcpcm_delete_products_batch', array($this, 'handle_delete_products_batch_ajax'));
  34. // Add a test AJAX handler to verify AJAX is working
  35. add_action('wp_ajax_studiou_wcpcm_test', array($this, 'handle_test_ajax'));
  36. // Debug: Log that the handler is being registered
  37. if (defined('WP_DEBUG') && WP_DEBUG) {
  38. error_log('STUDIOU WC: AJAX handlers registered - move_products, batch_description, and delete_products');
  39. }
  40. }
  41. /**
  42. * Handle AJAX batch description request
  43. */
  44. public function handle_batch_description_ajax() {
  45. // Clean all output buffers and prevent any output
  46. while (ob_get_level()) {
  47. ob_end_clean();
  48. }
  49. // Start a new output buffer to catch any unwanted output
  50. ob_start();
  51. // Suppress PHP errors/notices during AJAX to prevent JSON corruption
  52. $original_error_reporting = error_reporting();
  53. error_reporting(0);
  54. // Enable error logging only
  55. if (defined('WP_DEBUG') && WP_DEBUG) {
  56. error_log('STUDIOU WC: Batch description AJAX handler called');
  57. error_log('STUDIOU WC: POST data: ' . print_r($_POST, true));
  58. }
  59. try {
  60. // Check nonce
  61. if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'studiou-wcpcm-nonce')) {
  62. wp_send_json_error(array(
  63. 'message' => __('Security check failed', 'studiou-wc-product-cat-manage')
  64. ));
  65. }
  66. // Check permissions
  67. if (!current_user_can('manage_woocommerce')) {
  68. wp_send_json_error(array(
  69. 'message' => __('You do not have sufficient permissions', 'studiou-wc-product-cat-manage')
  70. ));
  71. }
  72. // Validate input
  73. if (!isset($_POST['selected_categories']) || !isset($_POST['description'])) {
  74. wp_send_json_error(array(
  75. 'message' => __('Missing required parameters', 'studiou-wc-product-cat-manage')
  76. ));
  77. }
  78. $selected_categories = array_map('intval', $_POST['selected_categories']);
  79. $description = sanitize_textarea_field($_POST['description']);
  80. // Debug logging
  81. if (defined('WP_DEBUG') && WP_DEBUG) {
  82. error_log('STUDIOU WC: Batch description operation started - Categories: ' . implode(', ', $selected_categories));
  83. error_log('STUDIOU WC: Description: ' . $description);
  84. }
  85. // Validate categories
  86. if (empty($selected_categories)) {
  87. wp_send_json_error(array(
  88. 'message' => __('Please select at least one category', 'studiou-wc-product-cat-manage')
  89. ));
  90. }
  91. // Execute batch description operation
  92. $result = $this->update_categories_description($selected_categories, $description);
  93. // Debug logging
  94. if (defined('WP_DEBUG') && WP_DEBUG) {
  95. error_log('STUDIOU WC: Batch description operation completed - Updated: ' . $result['updated_count'] . ' categories');
  96. error_log('STUDIOU WC: Sending success response: ' . print_r($result, true));
  97. }
  98. // Clean any output that might have been generated
  99. ob_clean();
  100. // Return success response
  101. wp_send_json_success(array(
  102. 'message' => $result['message'],
  103. 'updated_count' => $result['updated_count'],
  104. 'failed_count' => isset($result['failed_count']) ? $result['failed_count'] : 0
  105. ));
  106. } catch (Exception $e) {
  107. // Debug logging
  108. if (defined('WP_DEBUG') && WP_DEBUG) {
  109. error_log('STUDIOU WC: Batch description operation failed - ' . $e->getMessage());
  110. error_log('STUDIOU WC: Exception trace: ' . $e->getTraceAsString());
  111. }
  112. // Clean any output that might have been generated
  113. ob_clean();
  114. wp_send_json_error(array(
  115. 'message' => sprintf(
  116. __('Error during batch description operation: %s', 'studiou-wc-product-cat-manage'),
  117. $e->getMessage()
  118. )
  119. ));
  120. } finally {
  121. // Restore original error reporting
  122. error_reporting($original_error_reporting);
  123. // End the output buffer
  124. ob_end_clean();
  125. }
  126. }
  127. /**
  128. * Handle AJAX move products request
  129. */
  130. public function handle_move_products_ajax() {
  131. // Clean all output buffers and prevent any output
  132. while (ob_get_level()) {
  133. ob_end_clean();
  134. }
  135. // Start a new output buffer to catch any unwanted output
  136. ob_start();
  137. // Suppress PHP errors/notices during AJAX to prevent JSON corruption
  138. $original_error_reporting = error_reporting();
  139. error_reporting(0);
  140. // Enable error logging only
  141. if (defined('WP_DEBUG') && WP_DEBUG) {
  142. error_log('STUDIOU WC: AJAX handler called');
  143. error_log('STUDIOU WC: POST data: ' . print_r($_POST, true));
  144. }
  145. try {
  146. // Check nonce
  147. if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'studiou-wcpcm-nonce')) {
  148. wp_send_json_error(array(
  149. 'message' => __('Security check failed', 'studiou-wc-product-cat-manage')
  150. ));
  151. }
  152. // Check permissions
  153. if (!current_user_can('manage_woocommerce')) {
  154. wp_send_json_error(array(
  155. 'message' => __('You do not have sufficient permissions', 'studiou-wc-product-cat-manage')
  156. ));
  157. }
  158. // Validate input
  159. if (!isset($_POST['source_category']) || !isset($_POST['target_category'])) {
  160. wp_send_json_error(array(
  161. 'message' => __('Missing required parameters', 'studiou-wc-product-cat-manage')
  162. ));
  163. }
  164. $source_category_id = intval($_POST['source_category']);
  165. $target_category_id = intval($_POST['target_category']);
  166. // Debug logging
  167. if (defined('WP_DEBUG') && WP_DEBUG) {
  168. error_log('STUDIOU WC: Move operation started - Source: ' . $source_category_id . ', Target: ' . $target_category_id);
  169. }
  170. // Validate categories
  171. if ($source_category_id === $target_category_id) {
  172. wp_send_json_error(array(
  173. 'message' => __('Source and target categories must be different', 'studiou-wc-product-cat-manage')
  174. ));
  175. }
  176. if ($source_category_id <= 0 || $target_category_id <= 0) {
  177. wp_send_json_error(array(
  178. 'message' => __('Invalid category selection', 'studiou-wc-product-cat-manage')
  179. ));
  180. }
  181. // Execute move operation
  182. $result = $this->move_products_between_categories($source_category_id, $target_category_id);
  183. // Debug logging
  184. if (defined('WP_DEBUG') && WP_DEBUG) {
  185. error_log('STUDIOU WC: Move operation completed - Moved: ' . $result['moved_count'] . ' products');
  186. error_log('STUDIOU WC: Sending success response: ' . print_r($result, true));
  187. }
  188. // Clean any output that might have been generated
  189. ob_clean();
  190. // Return success response
  191. wp_send_json_success(array(
  192. 'message' => $result['message'],
  193. 'moved_count' => $result['moved_count'],
  194. 'source_count' => $result['source_count'],
  195. 'target_count' => $result['target_count'],
  196. 'failed_count' => isset($result['failed_count']) ? $result['failed_count'] : 0
  197. ));
  198. } catch (Exception $e) {
  199. // Debug logging
  200. if (defined('WP_DEBUG') && WP_DEBUG) {
  201. error_log('STUDIOU WC: Move operation failed - ' . $e->getMessage());
  202. error_log('STUDIOU WC: Exception trace: ' . $e->getTraceAsString());
  203. }
  204. // Clean any output that might have been generated
  205. ob_clean();
  206. wp_send_json_error(array(
  207. 'message' => sprintf(
  208. __('Error during move operation: %s', 'studiou-wc-product-cat-manage'),
  209. $e->getMessage()
  210. )
  211. ));
  212. } finally {
  213. // Restore original error reporting
  214. error_reporting($original_error_reporting);
  215. // End the output buffer
  216. ob_end_clean();
  217. }
  218. }
  219. /**
  220. * Move all products from source category to target category
  221. *
  222. * @param int $source_category_id Source category ID
  223. * @param int $target_category_id Target category ID
  224. * @return array Result with counts and message
  225. * @throws Exception On error
  226. */
  227. public function move_products_between_categories($source_category_id, $target_category_id) {
  228. // Get source and target category terms
  229. $source_category = get_term($source_category_id, 'product_cat');
  230. $target_category = get_term($target_category_id, 'product_cat');
  231. if (is_wp_error($source_category) || is_wp_error($target_category)) {
  232. throw new Exception(__('Invalid category specified', 'studiou-wc-product-cat-manage'));
  233. }
  234. // Get initial count of products in source category using direct DB query
  235. $initial_source_count = $this->get_category_product_count($source_category_id);
  236. // Check if source category has any products
  237. if ($initial_source_count === 0) {
  238. $message = sprintf(
  239. __('No products found in source category "%s". Nothing to move.', 'studiou-wc-product-cat-manage'),
  240. $source_category->name
  241. );
  242. return array(
  243. 'moved_count' => 0,
  244. 'source_count' => 0,
  245. 'target_count' => $this->get_category_product_count($target_category_id),
  246. 'message' => $message
  247. );
  248. }
  249. // Get all products in source category using direct DB query
  250. $products = $this->get_products_in_category($source_category_id);
  251. $moved_count = 0;
  252. $failed_count = 0;
  253. if (defined('WP_DEBUG') && WP_DEBUG) {
  254. error_log('STUDIOU WC: Found ' . count($products) . ' products in category ' . $source_category->name . ' for moving');
  255. }
  256. // Move each product
  257. foreach ($products as $product_id) {
  258. // Get current categories for the product
  259. $current_categories = wp_get_post_terms($product_id, 'product_cat', array('fields' => 'ids'));
  260. if (is_wp_error($current_categories)) {
  261. $failed_count++;
  262. if (defined('WP_DEBUG') && WP_DEBUG) {
  263. error_log('STUDIOU WC: Error getting categories for product ' . $product_id . ': ' . $current_categories->get_error_message());
  264. }
  265. continue;
  266. }
  267. // Remove source category and add target category
  268. $new_categories = array_diff($current_categories, array($source_category_id));
  269. $new_categories[] = $target_category_id;
  270. // Update product categories
  271. $result = wp_set_post_terms($product_id, $new_categories, 'product_cat');
  272. if (!is_wp_error($result)) {
  273. $moved_count++;
  274. if (defined('WP_DEBUG') && WP_DEBUG) {
  275. error_log('STUDIOU WC: Successfully moved product ' . $product_id . ' from category ' . $source_category_id . ' to ' . $target_category_id);
  276. }
  277. } else {
  278. $failed_count++;
  279. if (defined('WP_DEBUG') && WP_DEBUG) {
  280. error_log('STUDIOU WC: Error moving product ' . $product_id . ': ' . $result->get_error_message());
  281. }
  282. }
  283. }
  284. // Get updated counts using direct DB query
  285. $source_count = $this->get_category_product_count($source_category_id);
  286. $target_count = $this->get_category_product_count($target_category_id);
  287. // Generate detailed result message
  288. if ($moved_count === 0 && count($products) > 0) {
  289. $message = sprintf(
  290. __('Failed to move any products from "%s" to "%s". All %d products encountered errors during the move operation.', 'studiou-wc-product-cat-manage'),
  291. $source_category->name,
  292. $target_category->name,
  293. count($products)
  294. );
  295. } else {
  296. $message = sprintf(
  297. __('Moved %d products from "%s" to "%s". Source category now has %d products, target category now has %d products.', 'studiou-wc-product-cat-manage'),
  298. $moved_count,
  299. $source_category->name,
  300. $target_category->name,
  301. $source_count,
  302. $target_count
  303. );
  304. if ($failed_count > 0) {
  305. $message .= ' ' . sprintf(
  306. __('Note: %d products failed to move due to errors.', 'studiou-wc-product-cat-manage'),
  307. $failed_count
  308. );
  309. }
  310. }
  311. return array(
  312. 'moved_count' => $moved_count,
  313. 'source_count' => $source_count,
  314. 'target_count' => $target_count,
  315. 'failed_count' => $failed_count,
  316. 'message' => $message
  317. );
  318. }
  319. /**
  320. * Get product count for a category
  321. *
  322. * @param int $category_id Category ID
  323. * @return int Product count
  324. */
  325. private function get_category_product_count($category_id) {
  326. global $wpdb;
  327. // Use direct database query for more accurate counting
  328. $sql = "
  329. SELECT COUNT(DISTINCT p.ID)
  330. FROM {$wpdb->posts} p
  331. INNER JOIN {$wpdb->term_relationships} tr ON p.ID = tr.object_id
  332. INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
  333. WHERE tt.term_id = %d
  334. AND tt.taxonomy = 'product_cat'
  335. AND p.post_type = 'product'
  336. AND p.post_status IN ('publish', 'private', 'draft')
  337. ";
  338. $count = $wpdb->get_var($wpdb->prepare($sql, $category_id));
  339. if (defined('WP_DEBUG') && WP_DEBUG) {
  340. error_log('STUDIOU WC: Category ' . $category_id . ' product count (direct DB query): ' . intval($count));
  341. }
  342. return intval($count);
  343. }
  344. /**
  345. * Get all products in a category
  346. *
  347. * @param int $category_id Category ID
  348. * @return array Array of product IDs
  349. */
  350. private function get_products_in_category($category_id) {
  351. global $wpdb;
  352. // Use direct database query to get products
  353. $sql = "
  354. SELECT DISTINCT p.ID
  355. FROM {$wpdb->posts} p
  356. INNER JOIN {$wpdb->term_relationships} tr ON p.ID = tr.object_id
  357. INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
  358. WHERE tt.term_id = %d
  359. AND tt.taxonomy = 'product_cat'
  360. AND p.post_type = 'product'
  361. AND p.post_status IN ('publish', 'private', 'draft')
  362. ";
  363. $products = $wpdb->get_col($wpdb->prepare($sql, $category_id));
  364. if (defined('WP_DEBUG') && WP_DEBUG) {
  365. error_log('STUDIOU WC: Found ' . count($products) . ' products in category ' . $category_id . ' (direct DB query)');
  366. if (!empty($products)) {
  367. error_log('STUDIOU WC: Product IDs: ' . implode(', ', array_slice($products, 0, 10)) . (count($products) > 10 ? ' (showing first 10)' : ''));
  368. }
  369. }
  370. return $products ? array_map('intval', $products) : array();
  371. }
  372. /**
  373. * Update descriptions for multiple categories
  374. *
  375. * @param array $category_ids Array of category IDs
  376. * @param string $description New description for categories
  377. * @return array Result with counts and message
  378. * @throws Exception On error
  379. */
  380. public function update_categories_description($category_ids, $description) {
  381. $updated_count = 0;
  382. $failed_count = 0;
  383. $category_names = array();
  384. // Debug logging
  385. if (defined('WP_DEBUG') && WP_DEBUG) {
  386. error_log('STUDIOU WC: Starting batch description update for ' . count($category_ids) . ' categories');
  387. }
  388. // Update each category
  389. foreach ($category_ids as $category_id) {
  390. // Get category term
  391. $category = get_term($category_id, 'product_cat');
  392. if (is_wp_error($category) || !$category) {
  393. $failed_count++;
  394. if (defined('WP_DEBUG') && WP_DEBUG) {
  395. error_log('STUDIOU WC: Error getting category ' . $category_id . ': ' . ($category ? $category->get_error_message() : 'Category not found'));
  396. }
  397. continue;
  398. }
  399. // Update category description
  400. $result = wp_update_term($category_id, 'product_cat', array(
  401. 'description' => $description
  402. ));
  403. if (!is_wp_error($result)) {
  404. $updated_count++;
  405. $category_names[] = $category->name;
  406. if (defined('WP_DEBUG') && WP_DEBUG) {
  407. error_log('STUDIOU WC: Successfully updated description for category ' . $category_id . ' (' . $category->name . ')');
  408. }
  409. } else {
  410. $failed_count++;
  411. if (defined('WP_DEBUG') && WP_DEBUG) {
  412. error_log('STUDIOU WC: Error updating category ' . $category_id . ': ' . $result->get_error_message());
  413. }
  414. }
  415. }
  416. // Generate result message
  417. if ($updated_count === 0) {
  418. $message = sprintf(
  419. __('Failed to update any category descriptions. %d categories encountered errors.', 'studiou-wc-product-cat-manage'),
  420. $failed_count
  421. );
  422. } else {
  423. $message = sprintf(
  424. __('Successfully updated descriptions for %d categories: %s', 'studiou-wc-product-cat-manage'),
  425. $updated_count,
  426. implode(', ', array_slice($category_names, 0, 5)) . ($updated_count > 5 ? sprintf(__(', and %d more', 'studiou-wc-product-cat-manage'), $updated_count - 5) : '')
  427. );
  428. if ($failed_count > 0) {
  429. $message .= ' ' . sprintf(
  430. __('Note: %d categories failed to update due to errors.', 'studiou-wc-product-cat-manage'),
  431. $failed_count
  432. );
  433. }
  434. }
  435. return array(
  436. 'updated_count' => $updated_count,
  437. 'failed_count' => $failed_count,
  438. 'message' => $message
  439. );
  440. }
  441. /**
  442. * Get all categories with product counts for select lists
  443. *
  444. * @return array Array of categories with ID, name, and product count
  445. */
  446. public function get_categories_with_counts() {
  447. $args = array(
  448. 'taxonomy' => 'product_cat',
  449. 'orderby' => 'name',
  450. 'hide_empty' => false,
  451. );
  452. $categories = get_terms($args);
  453. $categories_with_counts = array();
  454. if (!is_wp_error($categories)) {
  455. foreach ($categories as $category) {
  456. $product_count = $this->get_category_product_count($category->term_id);
  457. $categories_with_counts[] = array(
  458. 'id' => $category->term_id,
  459. 'name' => $category->name,
  460. 'count' => $product_count,
  461. 'display_name' => sprintf('%s (%d products)', $category->name, $product_count)
  462. );
  463. }
  464. }
  465. return $categories_with_counts;
  466. }
  467. /**
  468. * Handle AJAX delete products request (initial request to get product IDs)
  469. */
  470. public function handle_delete_products_ajax() {
  471. // Clean all output buffers and prevent any output
  472. while (ob_get_level()) {
  473. ob_end_clean();
  474. }
  475. // Start a new output buffer to catch any unwanted output
  476. ob_start();
  477. // Suppress PHP errors/notices during AJAX to prevent JSON corruption
  478. $original_error_reporting = error_reporting();
  479. error_reporting(0);
  480. // Enable error logging only
  481. if (defined('WP_DEBUG') && WP_DEBUG) {
  482. error_log('STUDIOU WC: Delete products AJAX handler called');
  483. error_log('STUDIOU WC: POST data: ' . print_r($_POST, true));
  484. }
  485. try {
  486. // Check nonce
  487. if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'studiou-wcpcm-nonce')) {
  488. wp_send_json_error(array(
  489. 'message' => __('Security check failed', 'studiou-wc-product-cat-manage')
  490. ));
  491. }
  492. // Check permissions
  493. if (!current_user_can('manage_woocommerce')) {
  494. wp_send_json_error(array(
  495. 'message' => __('You do not have sufficient permissions', 'studiou-wc-product-cat-manage')
  496. ));
  497. }
  498. // Validate input
  499. if (!isset($_POST['delete_categories'])) {
  500. wp_send_json_error(array(
  501. 'message' => __('Missing required parameters', 'studiou-wc-product-cat-manage')
  502. ));
  503. }
  504. $category_ids = array_map('intval', $_POST['delete_categories']);
  505. // Debug logging
  506. if (defined('WP_DEBUG') && WP_DEBUG) {
  507. error_log('STUDIOU WC: Delete products operation started - Categories: ' . implode(', ', $category_ids));
  508. }
  509. // Validate categories
  510. if (empty($category_ids)) {
  511. wp_send_json_error(array(
  512. 'message' => __('Please select at least one category', 'studiou-wc-product-cat-manage')
  513. ));
  514. }
  515. // Get all products from selected categories
  516. $product_ids = $this->get_products_from_categories($category_ids);
  517. // Debug logging
  518. if (defined('WP_DEBUG') && WP_DEBUG) {
  519. error_log('STUDIOU WC: Found ' . count($product_ids) . ' products to delete');
  520. }
  521. // Clean any output that might have been generated
  522. ob_clean();
  523. // Return product IDs for batch processing
  524. wp_send_json_success(array(
  525. 'product_ids' => $product_ids,
  526. 'total_count' => count($product_ids),
  527. 'message' => sprintf(
  528. __('Found %d products to delete', 'studiou-wc-product-cat-manage'),
  529. count($product_ids)
  530. )
  531. ));
  532. } catch (Exception $e) {
  533. // Debug logging
  534. if (defined('WP_DEBUG') && WP_DEBUG) {
  535. error_log('STUDIOU WC: Delete products operation failed - ' . $e->getMessage());
  536. error_log('STUDIOU WC: Exception trace: ' . $e->getTraceAsString());
  537. }
  538. // Clean any output that might have been generated
  539. ob_clean();
  540. wp_send_json_error(array(
  541. 'message' => sprintf(
  542. __('Error during delete operation: %s', 'studiou-wc-product-cat-manage'),
  543. $e->getMessage()
  544. )
  545. ));
  546. } finally {
  547. // Restore original error reporting
  548. error_reporting($original_error_reporting);
  549. // End the output buffer
  550. ob_end_clean();
  551. }
  552. }
  553. /**
  554. * Handle AJAX delete products batch request (processes products in batches)
  555. */
  556. public function handle_delete_products_batch_ajax() {
  557. // Clean all output buffers and prevent any output
  558. while (ob_get_level()) {
  559. ob_end_clean();
  560. }
  561. // Start a new output buffer to catch any unwanted output
  562. ob_start();
  563. // Suppress PHP errors/notices during AJAX to prevent JSON corruption
  564. $original_error_reporting = error_reporting();
  565. error_reporting(0);
  566. // Enable error logging only
  567. if (defined('WP_DEBUG') && WP_DEBUG) {
  568. error_log('STUDIOU WC: Delete products batch AJAX handler called');
  569. error_log('STUDIOU WC: POST data: ' . print_r($_POST, true));
  570. }
  571. try {
  572. // Check nonce
  573. if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'studiou-wcpcm-nonce')) {
  574. wp_send_json_error(array(
  575. 'message' => __('Security check failed', 'studiou-wc-product-cat-manage')
  576. ));
  577. }
  578. // Check permissions
  579. if (!current_user_can('manage_woocommerce')) {
  580. wp_send_json_error(array(
  581. 'message' => __('You do not have sufficient permissions', 'studiou-wc-product-cat-manage')
  582. ));
  583. }
  584. // Validate input
  585. if (!isset($_POST['product_ids'])) {
  586. wp_send_json_error(array(
  587. 'message' => __('Missing required parameters', 'studiou-wc-product-cat-manage')
  588. ));
  589. }
  590. $product_ids = array_map('intval', $_POST['product_ids']);
  591. // Debug logging
  592. if (defined('WP_DEBUG') && WP_DEBUG) {
  593. error_log('STUDIOU WC: Processing batch of ' . count($product_ids) . ' products for deletion');
  594. }
  595. // Delete products in this batch
  596. $result = $this->delete_products_batch($product_ids);
  597. // Debug logging
  598. if (defined('WP_DEBUG') && WP_DEBUG) {
  599. error_log('STUDIOU WC: Batch deletion completed - Deleted: ' . $result['deleted_count'] . ', Failed: ' . $result['failed_count']);
  600. }
  601. // Clean any output that might have been generated
  602. ob_clean();
  603. // Return success response
  604. wp_send_json_success(array(
  605. 'deleted_count' => $result['deleted_count'],
  606. 'failed_count' => $result['failed_count'],
  607. 'failed_products' => $result['failed_products']
  608. ));
  609. } catch (Exception $e) {
  610. // Debug logging
  611. if (defined('WP_DEBUG') && WP_DEBUG) {
  612. error_log('STUDIOU WC: Delete products batch operation failed - ' . $e->getMessage());
  613. error_log('STUDIOU WC: Exception trace: ' . $e->getTraceAsString());
  614. }
  615. // Clean any output that might have been generated
  616. ob_clean();
  617. wp_send_json_error(array(
  618. 'message' => sprintf(
  619. __('Error during delete batch operation: %s', 'studiou-wc-product-cat-manage'),
  620. $e->getMessage()
  621. )
  622. ));
  623. } finally {
  624. // Restore original error reporting
  625. error_reporting($original_error_reporting);
  626. // End the output buffer
  627. ob_end_clean();
  628. }
  629. }
  630. /**
  631. * Get all products from multiple categories
  632. *
  633. * @param array $category_ids Array of category IDs
  634. * @return array Array of unique product IDs (including parent products with variants)
  635. */
  636. private function get_products_from_categories($category_ids) {
  637. global $wpdb;
  638. if (empty($category_ids)) {
  639. return array();
  640. }
  641. $placeholders = implode(',', array_fill(0, count($category_ids), '%d'));
  642. // Get all products from selected categories (both simple and parent products)
  643. $sql = "
  644. SELECT DISTINCT p.ID
  645. FROM {$wpdb->posts} p
  646. INNER JOIN {$wpdb->term_relationships} tr ON p.ID = tr.object_id
  647. INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
  648. WHERE tt.term_id IN ($placeholders)
  649. AND tt.taxonomy = 'product_cat'
  650. AND p.post_type = 'product'
  651. AND p.post_status IN ('publish', 'private', 'draft', 'pending', 'trash')
  652. ";
  653. $products = $wpdb->get_col($wpdb->prepare($sql, $category_ids));
  654. if (defined('WP_DEBUG') && WP_DEBUG) {
  655. error_log('STUDIOU WC: Found ' . count($products) . ' products in categories ' . implode(', ', $category_ids));
  656. }
  657. return $products ? array_map('intval', $products) : array();
  658. }
  659. /**
  660. * Delete a batch of products and their variants
  661. *
  662. * @param array $product_ids Array of product IDs to delete
  663. * @return array Result with counts
  664. */
  665. private function delete_products_batch($product_ids) {
  666. $deleted_count = 0;
  667. $failed_count = 0;
  668. $failed_products = array();
  669. foreach ($product_ids as $product_id) {
  670. try {
  671. // Get the product object
  672. $product = wc_get_product($product_id);
  673. if (!$product) {
  674. $failed_count++;
  675. $failed_products[] = $product_id;
  676. if (defined('WP_DEBUG') && WP_DEBUG) {
  677. error_log('STUDIOU WC: Product ' . $product_id . ' not found');
  678. }
  679. continue;
  680. }
  681. // If it's a variable product, delete all variations first
  682. if ($product->is_type('variable')) {
  683. $variations = $product->get_children();
  684. if (defined('WP_DEBUG') && WP_DEBUG) {
  685. error_log('STUDIOU WC: Deleting ' . count($variations) . ' variations for product ' . $product_id);
  686. }
  687. foreach ($variations as $variation_id) {
  688. $variation = wc_get_product($variation_id);
  689. if ($variation) {
  690. $variation->delete(true); // true = force delete permanently
  691. }
  692. }
  693. }
  694. // Delete the product permanently
  695. $result = $product->delete(true); // true = force delete permanently
  696. if ($result) {
  697. $deleted_count++;
  698. if (defined('WP_DEBUG') && WP_DEBUG) {
  699. error_log('STUDIOU WC: Successfully deleted product ' . $product_id);
  700. }
  701. } else {
  702. $failed_count++;
  703. $failed_products[] = $product_id;
  704. if (defined('WP_DEBUG') && WP_DEBUG) {
  705. error_log('STUDIOU WC: Failed to delete product ' . $product_id);
  706. }
  707. }
  708. } catch (Exception $e) {
  709. $failed_count++;
  710. $failed_products[] = $product_id;
  711. if (defined('WP_DEBUG') && WP_DEBUG) {
  712. error_log('STUDIOU WC: Exception deleting product ' . $product_id . ': ' . $e->getMessage());
  713. }
  714. }
  715. }
  716. return array(
  717. 'deleted_count' => $deleted_count,
  718. 'failed_count' => $failed_count,
  719. 'failed_products' => $failed_products
  720. );
  721. }
  722. }