class-studiou-wc-fpp-admin.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. <?php
  2. if (!defined('WPINC')) {
  3. die;
  4. }
  5. class Studiou_WC_FPP_Admin {
  6. /** @var Studiou_WC_FPP_DB */
  7. private $db;
  8. public function __construct($db) {
  9. $this->db = $db;
  10. // AJAX handlers
  11. add_action('wp_ajax_studiou_wcfpp_update_status', array($this, 'ajax_update_status'));
  12. add_action('wp_ajax_studiou_wcfpp_delete_files', array($this, 'ajax_delete_files'));
  13. add_action('wp_ajax_studiou_wcfpp_download_zip', array($this, 'ajax_download_zip'));
  14. add_action('wp_ajax_studiou_wcfpp_bulk_status', array($this, 'ajax_bulk_status'));
  15. // Handle direct ZIP download and file download (marks as downloaded)
  16. add_action('admin_init', array($this, 'handle_zip_download'));
  17. add_action('admin_init', array($this, 'handle_file_download'));
  18. }
  19. public function render_summary_page() {
  20. // Process filters
  21. $current_status = isset($_GET['status']) ? sanitize_text_field($_GET['status']) : '';
  22. $current_product = isset($_GET['product_id']) ? absint($_GET['product_id']) : 0;
  23. $current_search = isset($_GET['s']) ? sanitize_text_field($_GET['s']) : '';
  24. $current_page = isset($_GET['paged']) ? max(1, absint($_GET['paged'])) : 1;
  25. $per_page = 30;
  26. $args = array(
  27. 'status' => $current_status,
  28. 'product_id' => $current_product,
  29. 'search' => $current_search,
  30. 'limit' => $per_page,
  31. 'offset' => ($current_page - 1) * $per_page,
  32. 'orderby' => isset($_GET['orderby']) ? sanitize_text_field($_GET['orderby']) : 'created_at',
  33. 'order' => isset($_GET['order']) ? sanitize_text_field($_GET['order']) : 'DESC',
  34. );
  35. $records = $this->db->get_file_records($args);
  36. $total = $this->db->count_file_records($args);
  37. $total_pages = ceil($total / $per_page);
  38. // Get products that have FPP enabled for filter dropdown
  39. $fpp_products = $this->get_fpp_products();
  40. include STUDIOU_WCFPP_PLUGIN_DIR . 'views/admin-summary.php';
  41. }
  42. private function get_fpp_products() {
  43. $args = array(
  44. 'post_type' => 'product',
  45. 'post_status' => 'publish',
  46. 'posts_per_page' => -1,
  47. 'meta_query' => array(
  48. array(
  49. 'key' => '_studiou_fpp_enabled',
  50. 'value' => 'yes',
  51. ),
  52. ),
  53. 'fields' => 'ids',
  54. );
  55. $product_ids = get_posts($args);
  56. $products = array();
  57. foreach ($product_ids as $pid) {
  58. $products[$pid] = get_the_title($pid);
  59. }
  60. return $products;
  61. }
  62. public function ajax_update_status() {
  63. check_ajax_referer('studiou-wcfpp-nonce', 'nonce');
  64. if (!current_user_can('manage_woocommerce')) {
  65. wp_send_json_error(array('message' => __('Insufficient permissions.', 'studiou-wc-free-photo-product')));
  66. return;
  67. }
  68. $file_id = isset($_POST['file_id']) ? absint($_POST['file_id']) : 0;
  69. $status = isset($_POST['status']) ? sanitize_text_field($_POST['status']) : '';
  70. if (!$file_id || !$status) {
  71. wp_send_json_error(array('message' => __('Invalid data.', 'studiou-wc-free-photo-product')));
  72. return;
  73. }
  74. $result = $this->db->update_file_status($file_id, $status);
  75. if ($result === false) {
  76. wp_send_json_error(array('message' => __('Failed to update status.', 'studiou-wc-free-photo-product')));
  77. return;
  78. }
  79. wp_send_json_success(array('message' => __('Status updated.', 'studiou-wc-free-photo-product')));
  80. }
  81. public function ajax_bulk_status() {
  82. check_ajax_referer('studiou-wcfpp-nonce', 'nonce');
  83. if (!current_user_can('manage_woocommerce')) {
  84. wp_send_json_error(array('message' => __('Insufficient permissions.', 'studiou-wc-free-photo-product')));
  85. return;
  86. }
  87. $file_ids = isset($_POST['file_ids']) ? array_map('absint', (array) $_POST['file_ids']) : array();
  88. $status = isset($_POST['status']) ? sanitize_text_field($_POST['status']) : '';
  89. if (empty($file_ids) || !$status) {
  90. wp_send_json_error(array('message' => __('Invalid data.', 'studiou-wc-free-photo-product')));
  91. return;
  92. }
  93. $updated = 0;
  94. foreach ($file_ids as $fid) {
  95. if ($this->db->update_file_status($fid, $status)) {
  96. $updated++;
  97. }
  98. }
  99. wp_send_json_success(array(
  100. 'message' => sprintf(__('%d file(s) updated.', 'studiou-wc-free-photo-product'), $updated),
  101. ));
  102. }
  103. public function ajax_delete_files() {
  104. check_ajax_referer('studiou-wcfpp-nonce', 'nonce');
  105. if (!current_user_can('manage_woocommerce')) {
  106. wp_send_json_error(array('message' => __('Insufficient permissions.', 'studiou-wc-free-photo-product')));
  107. return;
  108. }
  109. $file_ids = isset($_POST['file_ids']) ? array_map('absint', (array) $_POST['file_ids']) : array();
  110. if (empty($file_ids)) {
  111. wp_send_json_error(array('message' => __('No files selected.', 'studiou-wc-free-photo-product')));
  112. return;
  113. }
  114. $deleted = 0;
  115. foreach ($file_ids as $fid) {
  116. if ($this->db->delete_file_record($fid)) {
  117. $deleted++;
  118. }
  119. }
  120. wp_send_json_success(array(
  121. 'message' => sprintf(__('%d file(s) deleted.', 'studiou-wc-free-photo-product'), $deleted),
  122. ));
  123. }
  124. public function ajax_download_zip() {
  125. check_ajax_referer('studiou-wcfpp-nonce', 'nonce');
  126. if (!current_user_can('manage_woocommerce')) {
  127. wp_send_json_error(array('message' => __('Insufficient permissions.', 'studiou-wc-free-photo-product')));
  128. return;
  129. }
  130. $file_ids = isset($_POST['file_ids']) ? array_map('absint', (array) $_POST['file_ids']) : array();
  131. if (empty($file_ids)) {
  132. wp_send_json_error(array('message' => __('No files selected.', 'studiou-wc-free-photo-product')));
  133. return;
  134. }
  135. $records = $this->db->get_file_records_by_ids($file_ids);
  136. if (empty($records)) {
  137. wp_send_json_error(array('message' => __('No files found.', 'studiou-wc-free-photo-product')));
  138. return;
  139. }
  140. // Generate ZIP
  141. $upload_dir = wp_upload_dir();
  142. $zip_name = 'fpp-files-' . date('Y-m-d-His') . '.zip';
  143. $zip_path = $upload_dir['basedir'] . '/studiou-fpp-chunks/' . $zip_name;
  144. if (!class_exists('ZipArchive')) {
  145. wp_send_json_error(array('message' => __('ZIP extension not available on server.', 'studiou-wc-free-photo-product')));
  146. return;
  147. }
  148. $zip = new ZipArchive();
  149. if ($zip->open($zip_path, ZipArchive::CREATE) !== true) {
  150. wp_send_json_error(array('message' => __('Could not create ZIP file.', 'studiou-wc-free-photo-product')));
  151. return;
  152. }
  153. $added = 0;
  154. foreach ($records as $record) {
  155. $file_path = get_attached_file($record->attachment_id);
  156. if ($file_path && file_exists($file_path)) {
  157. // Use order number prefix if available
  158. $prefix = $record->order_id ? 'order-' . $record->order_id . '_' : '';
  159. $zip->addFile($file_path, $prefix . $record->file_name);
  160. $added++;
  161. }
  162. }
  163. $zip->close();
  164. if ($added === 0) {
  165. if (file_exists($zip_path)) {
  166. unlink($zip_path);
  167. }
  168. wp_send_json_error(array('message' => __('No files could be added to archive.', 'studiou-wc-free-photo-product')));
  169. return;
  170. }
  171. // Generate a nonce-protected download URL
  172. $download_url = add_query_arg(array(
  173. 'studiou_fpp_download' => 1,
  174. 'file' => $zip_name,
  175. '_wpnonce' => wp_create_nonce('studiou-fpp-zip-download'),
  176. ), admin_url('admin.php'));
  177. wp_send_json_success(array(
  178. 'download_url' => $download_url,
  179. 'file_count' => $added,
  180. ));
  181. }
  182. public function handle_zip_download() {
  183. if (!isset($_GET['studiou_fpp_download']) || !isset($_GET['file'])) {
  184. return;
  185. }
  186. if (!wp_verify_nonce($_GET['_wpnonce'], 'studiou-fpp-zip-download')) {
  187. wp_die(__('Security check failed.', 'studiou-wc-free-photo-product'));
  188. }
  189. if (!current_user_can('manage_woocommerce')) {
  190. wp_die(__('Insufficient permissions.', 'studiou-wc-free-photo-product'));
  191. }
  192. $file_name = sanitize_file_name($_GET['file']);
  193. $upload_dir = wp_upload_dir();
  194. $file_path = $upload_dir['basedir'] . '/studiou-fpp-chunks/' . $file_name;
  195. if (!file_exists($file_path) || pathinfo($file_name, PATHINFO_EXTENSION) !== 'zip') {
  196. wp_die(__('File not found.', 'studiou-wc-free-photo-product'));
  197. }
  198. header('Content-Type: application/zip');
  199. header('Content-Disposition: attachment; filename="' . $file_name . '"');
  200. header('Content-Length: ' . filesize($file_path));
  201. header('Pragma: no-cache');
  202. readfile($file_path);
  203. // Clean up the ZIP file after download
  204. unlink($file_path);
  205. exit;
  206. }
  207. /**
  208. * Handle single file download — serves the file and marks status as "downloaded".
  209. */
  210. public function handle_file_download() {
  211. if (!isset($_GET['studiou_fpp_download_file'])) {
  212. return;
  213. }
  214. if (!wp_verify_nonce($_GET['_wpnonce'] ?? '', 'studiou-fpp-download-file')) {
  215. wp_die(__('Security check failed.', 'studiou-wc-free-photo-product'));
  216. }
  217. if (!current_user_can('manage_woocommerce')) {
  218. wp_die(__('Insufficient permissions.', 'studiou-wc-free-photo-product'));
  219. }
  220. $file_record_id = isset($_GET['file_record_id']) ? absint($_GET['file_record_id']) : 0;
  221. $attachment_id = isset($_GET['attachment_id']) ? absint($_GET['attachment_id']) : 0;
  222. if (!$attachment_id) {
  223. wp_die(__('File not found.', 'studiou-wc-free-photo-product'));
  224. }
  225. // Mark as downloaded
  226. if ($file_record_id) {
  227. $record = $this->db->get_file_record($file_record_id);
  228. if ($record && $record->status === 'ready') {
  229. $this->db->update_file_status($file_record_id, 'downloaded');
  230. }
  231. }
  232. // Serve the file
  233. $file_path = get_attached_file($attachment_id);
  234. if (!$file_path || !file_exists($file_path)) {
  235. wp_die(__('File not found.', 'studiou-wc-free-photo-product'));
  236. }
  237. $file_name = basename($file_path);
  238. $mime_type = mime_content_type($file_path) ?: 'application/octet-stream';
  239. header('Content-Type: ' . $mime_type);
  240. header('Content-Disposition: attachment; filename="' . $file_name . '"');
  241. header('Content-Length: ' . filesize($file_path));
  242. header('Pragma: no-cache');
  243. readfile($file_path);
  244. exit;
  245. }
  246. public static function get_status_label($status) {
  247. $labels = array(
  248. 'ready' => __('Ready', 'studiou-wc-free-photo-product'),
  249. 'downloaded' => __('Downloaded', 'studiou-wc-free-photo-product'),
  250. 'solved' => __('Solved', 'studiou-wc-free-photo-product'),
  251. );
  252. return isset($labels[$status]) ? $labels[$status] : $status;
  253. }
  254. public static function get_status_class($status) {
  255. $classes = array(
  256. 'ready' => 'studiou-fpp-status-ready',
  257. 'downloaded' => 'studiou-fpp-status-downloaded',
  258. 'solved' => 'studiou-fpp-status-solved',
  259. );
  260. return isset($classes[$status]) ? $classes[$status] : '';
  261. }
  262. public static function get_order_edit_url($order_id) {
  263. if (class_exists('Automattic\WooCommerce\Utilities\OrderUtil')
  264. && \Automattic\WooCommerce\Utilities\OrderUtil::custom_orders_table_usage_is_enabled()) {
  265. return admin_url('admin.php?page=wc-orders&action=edit&id=' . $order_id);
  266. }
  267. return admin_url('post.php?post=' . $order_id . '&action=edit');
  268. }
  269. }