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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  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 and remove UTF-8 BOM if present
  131. $bom = fread($file, 3);
  132. if ($bom !== "\xEF\xBB\xBF") {
  133. // Not a BOM, rewind the file pointer
  134. rewind($file);
  135. }
  136. // Read headers
  137. $headers = fgetcsv($file);
  138. if (!$headers) {
  139. fclose($file);
  140. throw new Exception(__('Failed to read CSV headers', 'studiou-wc-product-cat-manage'));
  141. }
  142. // Validate headers
  143. $this->validate_headers($headers);
  144. // Process rows
  145. $results = array();
  146. $row_number = 1; // Header row is 1
  147. while (($row = fgetcsv($file)) !== false) {
  148. $row_number++;
  149. try {
  150. // Map row data to associative array
  151. $category_data = array();
  152. foreach ($headers as $index => $header) {
  153. $category_data[$header] = isset($row[$index]) ? $row[$index] : '';
  154. }
  155. // Validate row data
  156. $this->validate_row_data($category_data, $row_number);
  157. // Process based on operation
  158. $operation = strtoupper(trim($category_data['operation']));
  159. switch ($operation) {
  160. case 'A':
  161. $result = $this->db->create_category($category_data);
  162. break;
  163. case 'U':
  164. $result = $this->db->update_category($category_data);
  165. break;
  166. case 'D':
  167. $result = $this->db->delete_category($category_data['name']);
  168. break;
  169. default:
  170. throw new Exception(sprintf(
  171. __('Invalid operation "%s" at row %d', 'studiou-wc-product-cat-manage'),
  172. $operation,
  173. $row_number
  174. ));
  175. }
  176. $results[] = $result;
  177. } catch (Exception $e) {
  178. $results[] = array(
  179. 'status' => 'error',
  180. 'message' => sprintf(
  181. __('Error at row %d: %s', 'studiou-wc-product-cat-manage'),
  182. $row_number,
  183. $e->getMessage()
  184. )
  185. );
  186. }
  187. }
  188. fclose($file);
  189. return $results;
  190. }
  191. /**
  192. * Validate CSV headers
  193. *
  194. * @param array $headers Headers from CSV
  195. * @throws Exception If headers are invalid
  196. */
  197. private function validate_headers($headers) {
  198. // Check that all required headers are present
  199. foreach ($this->required_headers as $required_header) {
  200. if (!in_array($required_header, $headers)) {
  201. throw new Exception(sprintf(
  202. __('Missing required header: %s', 'studiou-wc-product-cat-manage'),
  203. $required_header
  204. ));
  205. }
  206. }
  207. }
  208. /**
  209. * Validate row data
  210. *
  211. * @param array $data Row data
  212. * @param int $row_number Row number for error messages
  213. * @throws Exception If data is invalid
  214. */
  215. private function validate_row_data($data, $row_number) {
  216. // Check required fields based on operation
  217. $operation = strtoupper(trim($data['operation']));
  218. if (!in_array($operation, $this->valid_operations)) {
  219. throw new Exception(sprintf(
  220. __('Invalid operation "%s" at row %d. Must be one of: A, U, D', 'studiou-wc-product-cat-manage'),
  221. $operation,
  222. $row_number
  223. ));
  224. }
  225. // Check name is required for all operations
  226. if (empty($data['name'])) {
  227. throw new Exception(sprintf(
  228. __('Missing required field "name" at row %d', 'studiou-wc-product-cat-manage'),
  229. $row_number
  230. ));
  231. }
  232. // Additional validations for Add and Update operations
  233. if ($operation === 'A' || $operation === 'U') {
  234. // URL name is required
  235. if (empty($data['url-name'])) {
  236. throw new Exception(sprintf(
  237. __('Missing required field "url-name" at row %d', 'studiou-wc-product-cat-manage'),
  238. $row_number
  239. ));
  240. }
  241. // Validate display type
  242. if (!empty($data['display-type']) && !in_array($data['display-type'], $this->valid_display_types)) {
  243. throw new Exception(sprintf(
  244. __('Invalid display type "%s" at row %d. Must be one of: default, products, subcategories, both', 'studiou-wc-product-cat-manage'),
  245. $data['display-type'],
  246. $row_number
  247. ));
  248. }
  249. // Validate visibility
  250. if (!empty($data['visibility']) && !in_array($data['visibility'], $this->valid_visibility)) {
  251. throw new Exception(sprintf(
  252. __('Invalid visibility "%s" at row %d. Must be one of: public, protected', 'studiou-wc-product-cat-manage'),
  253. $data['visibility'],
  254. $row_number
  255. ));
  256. }
  257. // If visibility is protected, passwords are required
  258. if (isset($data['visibility']) && $data['visibility'] === 'protected' && empty($data['protected-passwords'])) {
  259. throw new Exception(sprintf(
  260. __('Protected visibility requires at least one password at row %d', 'studiou-wc-product-cat-manage'),
  261. $row_number
  262. ));
  263. }
  264. }
  265. }
  266. /**
  267. * Generate import results message
  268. *
  269. * @param array $results Import results
  270. * @return string Formatted message
  271. */
  272. private function generate_import_results_message($results) {
  273. if (empty($results)) {
  274. return __('No items were processed.', 'studiou-wc-product-cat-manage');
  275. }
  276. $count = count($results);
  277. $message = sprintf(__('Imported %d items:', 'studiou-wc-product-cat-manage'), $count) . "\n";
  278. foreach ($results as $result) {
  279. $message .= "\t" . $result['message'] . "\n";
  280. }
  281. return $message;
  282. }
  283. }