class-studiou-wc-product-cat-manage-import.php 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. <?php
  2. /**
  3. * Import class
  4. *
  5. * Handles importing product categories from CSV
  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_Import {
  16. /**
  17. * Database handler
  18. *
  19. * @var Studiou_WC_Product_Cat_Manage_DB
  20. */
  21. private $db;
  22. /**
  23. * Required CSV headers
  24. *
  25. * @var array
  26. */
  27. private $required_headers = array(
  28. 'name',
  29. 'url-name',
  30. 'parent-name',
  31. 'description',
  32. 'display-type',
  33. 'visibility',
  34. 'protected-passwords',
  35. 'operation'
  36. );
  37. /**
  38. * Valid display types
  39. *
  40. * @var array
  41. */
  42. private $valid_display_types = array(
  43. 'default',
  44. 'products',
  45. 'subcategories',
  46. 'both'
  47. );
  48. /**
  49. * Valid visibility values
  50. *
  51. * @var array
  52. */
  53. private $valid_visibility = array(
  54. 'public',
  55. 'protected'
  56. );
  57. /**
  58. * Valid operation values
  59. *
  60. * @var array
  61. */
  62. private $valid_operations = array(
  63. 'A', // Add
  64. 'U', // Update
  65. 'D' // Delete
  66. );
  67. /**
  68. * Constructor
  69. *
  70. * @param Studiou_WC_Product_Cat_Manage_DB $db Database handler
  71. */
  72. public function __construct($db) {
  73. $this->db = $db;
  74. // Register AJAX handler
  75. add_action('wp_ajax_studiou_wcpcm_import', array($this, 'handle_import_ajax'));
  76. }
  77. /**
  78. * Handle AJAX import request
  79. */
  80. public function handle_import_ajax() {
  81. // Check nonce
  82. if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'studiou-wcpcm-nonce')) {
  83. wp_send_json_error(array(
  84. 'message' => __('Security check failed', 'studiou-wc-product-cat-manage')
  85. ));
  86. }
  87. // Check permissions
  88. if (!current_user_can('manage_woocommerce')) {
  89. wp_send_json_error(array(
  90. 'message' => __('You do not have sufficient permissions', 'studiou-wc-product-cat-manage')
  91. ));
  92. }
  93. // Check if file was uploaded
  94. if (!isset($_FILES['import_file']) || empty($_FILES['import_file']['tmp_name'])) {
  95. wp_send_json_error(array(
  96. 'message' => __('No file was uploaded', 'studiou-wc-product-cat-manage')
  97. ));
  98. }
  99. try {
  100. // Process the uploaded file
  101. $results = $this->process_import_file($_FILES['import_file']['tmp_name']);
  102. // Generate response message
  103. $message = $this->generate_import_results_message($results);
  104. // Return success response
  105. wp_send_json_success(array(
  106. 'message' => $message
  107. ));
  108. } catch (Exception $e) {
  109. wp_send_json_error(array(
  110. 'message' => sprintf(
  111. __('Import error: %s', 'studiou-wc-product-cat-manage'),
  112. $e->getMessage()
  113. )
  114. ));
  115. }
  116. }
  117. /**
  118. * Process import file
  119. *
  120. * @param string $file_path Path to uploaded CSV file
  121. * @return array Import results
  122. * @throws Exception On validation or processing error
  123. */
  124. private function process_import_file($file_path) {
  125. // Open file
  126. $file = fopen($file_path, 'r');
  127. if (!$file) {
  128. throw new Exception(__('Failed to open the uploaded file', 'studiou-wc-product-cat-manage'));
  129. }
  130. // Read headers
  131. $headers = fgetcsv($file);
  132. if (!$headers) {
  133. fclose($file);
  134. throw new Exception(__('Failed to read CSV headers', 'studiou-wc-product-cat-manage'));
  135. }
  136. // Validate headers
  137. $this->validate_headers($headers);
  138. // Process rows
  139. $results = array();
  140. $row_number = 1; // Header row is 1
  141. while (($row = fgetcsv($file)) !== false) {
  142. $row_number++;
  143. try {
  144. // Map row data to associative array
  145. $category_data = array();
  146. foreach ($headers as $index => $header) {
  147. $category_data[$header] = isset($row[$index]) ? $row[$index] : '';
  148. }
  149. // Validate row data
  150. $this->validate_row_data($category_data, $row_number);
  151. // Process based on operation
  152. $operation = strtoupper(trim($category_data['operation']));
  153. switch ($operation) {
  154. case 'A':
  155. $result = $this->db->create_category($category_data);
  156. break;
  157. case 'U':
  158. $result = $this->db->update_category($category_data);
  159. break;
  160. case 'D':
  161. $result = $this->db->delete_category($category_data['name']);
  162. break;
  163. default:
  164. throw new Exception(sprintf(
  165. __('Invalid operation "%s" at row %d', 'studiou-wc-product-cat-manage'),
  166. $operation,
  167. $row_number
  168. ));
  169. }
  170. $results[] = $result;
  171. } catch (Exception $e) {
  172. $results[] = array(
  173. 'status' => 'error',
  174. 'message' => sprintf(
  175. __('Error at row %d: %s', 'studiou-wc-product-cat-manage'),
  176. $row_number,
  177. $e->getMessage()
  178. )
  179. );
  180. }
  181. }
  182. fclose($file);
  183. return $results;
  184. }
  185. /**
  186. * Validate CSV headers
  187. *
  188. * @param array $headers Headers from CSV
  189. * @throws Exception If headers are invalid
  190. */
  191. private function validate_headers($headers) {
  192. // Check that all required headers are present
  193. foreach ($this->required_headers as $required_header) {
  194. if (!in_array($required_header, $headers)) {
  195. throw new Exception(sprintf(
  196. __('Missing required header: %s', 'studiou-wc-product-cat-manage'),
  197. $required_header
  198. ));
  199. }
  200. }
  201. }
  202. /**
  203. * Validate row data
  204. *
  205. * @param array $data Row data
  206. * @param int $row_number Row number for error messages
  207. * @throws Exception If data is invalid
  208. */
  209. private function validate_row_data($data, $row_number) {
  210. // Check required fields based on operation
  211. $operation = strtoupper(trim($data['operation']));
  212. if (!in_array($operation, $this->valid_operations)) {
  213. throw new Exception(sprintf(
  214. __('Invalid operation "%s" at row %d. Must be one of: A, U, D', 'studiou-wc-product-cat-manage'),
  215. $operation,
  216. $row_number
  217. ));
  218. }
  219. // Check name is required for all operations
  220. if (empty($data['name'])) {
  221. throw new Exception(sprintf(
  222. __('Missing required field "name" at row %d', 'studiou-wc-product-cat-manage'),
  223. $row_number
  224. ));
  225. }
  226. // Additional validations for Add and Update operations
  227. if ($operation === 'A' || $operation === 'U') {
  228. // URL name is required
  229. if (empty($data['url-name'])) {
  230. throw new Exception(sprintf(
  231. __('Missing required field "url-name" at row %d', 'studiou-wc-product-cat-manage'),
  232. $row_number
  233. ));
  234. }
  235. // Validate display type
  236. if (!empty($data['display-type']) && !in_array($data['display-type'], $this->valid_display_types)) {
  237. throw new Exception(sprintf(
  238. __('Invalid display type "%s" at row %d. Must be one of: default, products, subcategories, both', 'studiou-wc-product-cat-manage'),
  239. $data['display-type'],
  240. $row_number
  241. ));
  242. }
  243. // Validate visibility
  244. if (!empty($data['visibility']) && !in_array($data['visibility'], $this->valid_visibility)) {
  245. throw new Exception(sprintf(
  246. __('Invalid visibility "%s" at row %d. Must be one of: public, protected', 'studiou-wc-product-cat-manage'),
  247. $data['visibility'],
  248. $row_number
  249. ));
  250. }
  251. // If visibility is protected, passwords are required
  252. if ($data['visibility'] === 'protected' && empty($data['protected-passwords'])) {
  253. throw new Exception(sprintf(
  254. __('Protected visibility requires at least one password at row %d', 'studiou-wc-product-cat-manage'),
  255. $row_number
  256. ));
  257. }
  258. }
  259. }
  260. /**
  261. * Generate import results message
  262. *
  263. * @param array $results Import results
  264. * @return string Formatted message
  265. */
  266. private function generate_import_results_message($results) {
  267. if (empty($results)) {
  268. return __('No items were processed.', 'studiou-wc-product-cat-manage');
  269. }
  270. $count = count($results);
  271. $message = sprintf(__('Imported %d items:', 'studiou-wc-product-cat-manage'), $count) . "\n";
  272. foreach ($results as $result) {
  273. $message .= ' ' . $result['message'] . "\n";
  274. }
  275. return $message;
  276. }
  277. }