class-import-manager.php 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. <?php
  2. // Admin UI + handlers for importing the InPrint and Delivered protocol CSVs.
  3. defined('ABSPATH') || exit;
  4. class Import_Manager {
  5. public function __construct() {
  6. add_action('admin_menu', array($this, 'add_import_submenu'));
  7. add_action('admin_post_import_inprint_protocol', array($this, 'handle_import_inprint_protocol'));
  8. add_action('admin_post_import_delivered_protocol', array($this, 'handle_import_delivered_protocol'));
  9. }
  10. public function add_import_submenu() {
  11. add_submenu_page(
  12. 'woocommerce',
  13. __('Import Protocols', 'studiou-wc-ord-print-statuses'),
  14. __('Import Protocols', 'studiou-wc-ord-print-statuses'),
  15. 'manage_woocommerce',
  16. 'studiou-import-protocols',
  17. array($this, 'render_import_page')
  18. );
  19. }
  20. public function render_import_page() {
  21. ?>
  22. <div class="wrap">
  23. <h1><?php echo esc_html(get_admin_page_title()); ?></h1>
  24. <h2><?php esc_html_e('Import InPrint Protocol', 'studiou-wc-ord-print-statuses'); ?></h2>
  25. <p>
  26. <?php
  27. printf(
  28. /* translators: list of required CSV header names. */
  29. esc_html__('Required CSV headers: %s', 'studiou-wc-ord-print-statuses'),
  30. '<code>order_no</code>, <code>externalorder</code>, <code>externalorderdate</code>'
  31. );
  32. ?>
  33. </p>
  34. <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" enctype="multipart/form-data">
  35. <input type="hidden" name="action" value="import_inprint_protocol">
  36. <?php wp_nonce_field('import_inprint_protocol', 'import_inprint_protocol_nonce'); ?>
  37. <input type="file" name="inprint_csv" accept=".csv,text/csv" required>
  38. <?php submit_button(__('Import InPrint Protocol', 'studiou-wc-ord-print-statuses')); ?>
  39. </form>
  40. <h2><?php esc_html_e('Import Delivered Protocol', 'studiou-wc-ord-print-statuses'); ?></h2>
  41. <p>
  42. <?php
  43. printf(
  44. /* translators: list of required CSV header names. */
  45. esc_html__('Required CSV headers: %s', 'studiou-wc-ord-print-statuses'),
  46. '<code>order_no</code>'
  47. );
  48. ?>
  49. </p>
  50. <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" enctype="multipart/form-data">
  51. <input type="hidden" name="action" value="import_delivered_protocol">
  52. <?php wp_nonce_field('import_delivered_protocol', 'import_delivered_protocol_nonce'); ?>
  53. <input type="file" name="delivered_csv" accept=".csv,text/csv" required>
  54. <?php submit_button(__('Import Delivered Protocol', 'studiou-wc-ord-print-statuses')); ?>
  55. </form>
  56. </div>
  57. <?php
  58. }
  59. /**
  60. * Map from admin-post action name → [file field, nonce field/action, DB handler callable].
  61. * Adding a new protocol is a single-entry change here.
  62. */
  63. private function protocol_dispatch() {
  64. return array(
  65. 'import_inprint_protocol' => array(
  66. 'file_field' => 'inprint_csv',
  67. 'nonce' => 'import_inprint_protocol_nonce',
  68. 'handler' => array('Studiou_DB_Manager', 'import_inprint_protocol'),
  69. ),
  70. 'import_delivered_protocol' => array(
  71. 'file_field' => 'delivered_csv',
  72. 'nonce' => 'import_delivered_protocol_nonce',
  73. 'handler' => array('Studiou_DB_Manager', 'import_delivered_protocol'),
  74. ),
  75. );
  76. }
  77. public function handle_import_inprint_protocol() {
  78. $this->run_import('import_inprint_protocol');
  79. }
  80. public function handle_import_delivered_protocol() {
  81. $this->run_import('import_delivered_protocol');
  82. }
  83. /**
  84. * Common pipeline: capability check → nonce → file validation → parse → dispatch → redirect.
  85. * Every non-success branch returns immediately via redirect_back() so static
  86. * analysis can prove that subsequent code is only reached on the happy path.
  87. */
  88. private function run_import($action) {
  89. $dispatch = $this->protocol_dispatch();
  90. if (!isset($dispatch[$action])) {
  91. return $this->redirect_back();
  92. }
  93. $cfg = $dispatch[$action];
  94. if (!current_user_can('manage_woocommerce')) {
  95. wp_die(esc_html__('You do not have sufficient permissions to access this page.', 'studiou-wc-ord-print-statuses'));
  96. }
  97. check_admin_referer($action, $cfg['nonce']);
  98. $file = $this->validated_upload($cfg['file_field']);
  99. if ($file === null) {
  100. return $this->redirect_back();
  101. }
  102. $csv_data = Studiou_DB_Manager::parse_csv($file['tmp_name']);
  103. if (empty($csv_data)) {
  104. UtilsLog::message(
  105. __('CSV file could not be parsed or contained no data rows.', 'studiou-wc-ord-print-statuses'),
  106. 'error'
  107. );
  108. return $this->redirect_back();
  109. }
  110. $result = call_user_func($cfg['handler'], $csv_data);
  111. $this->emit_import_summary($result);
  112. return $this->redirect_back();
  113. }
  114. /**
  115. * Validate the file upload superglobal entry. Emits a notice and returns null on failure.
  116. *
  117. * @return array|null
  118. */
  119. private function validated_upload($field) {
  120. if (!isset($_FILES[$field]) || !is_array($_FILES[$field])) {
  121. UtilsLog::message(__('No file uploaded.', 'studiou-wc-ord-print-statuses'), 'error');
  122. return null;
  123. }
  124. $file = $_FILES[$field];
  125. if (isset($file['error']) && $file['error'] !== UPLOAD_ERR_OK) {
  126. UtilsLog::message(
  127. sprintf(
  128. /* translators: %d is the PHP upload error code. */
  129. __('File upload failed (error code %d).', 'studiou-wc-ord-print-statuses'),
  130. (int) $file['error']
  131. ),
  132. 'error'
  133. );
  134. return null;
  135. }
  136. if (empty($file['tmp_name']) || !is_uploaded_file($file['tmp_name'])) {
  137. UtilsLog::message(__('Uploaded file is not accessible.', 'studiou-wc-ord-print-statuses'), 'error');
  138. return null;
  139. }
  140. return $file;
  141. }
  142. private function emit_import_summary($result) {
  143. $updated = isset($result['updated_count']) ? (int) $result['updated_count'] : 0;
  144. $errors = isset($result['errors']) && is_array($result['errors']) ? $result['errors'] : array();
  145. UtilsLog::message(
  146. sprintf(
  147. _n(
  148. '%d order updated successfully.',
  149. '%d orders updated successfully.',
  150. $updated,
  151. 'studiou-wc-ord-print-statuses'
  152. ),
  153. $updated
  154. ),
  155. $updated > 0 ? 'success' : 'info'
  156. );
  157. if (!empty($errors)) {
  158. UtilsLog::message(
  159. sprintf(
  160. /* translators: 1: error count, 2: comma-separated error messages. */
  161. __('%1$d errors occurred: %2$s', 'studiou-wc-ord-print-statuses'),
  162. count($errors),
  163. esc_html(implode(', ', $errors))
  164. ),
  165. 'error'
  166. );
  167. }
  168. }
  169. private function redirect_back() {
  170. wp_safe_redirect(admin_url('admin.php?page=studiou-import-protocols'));
  171. exit;
  172. }
  173. }