db = $db; // AJAX handlers add_action('wp_ajax_studiou_wcfpp_update_status', array($this, 'ajax_update_status')); add_action('wp_ajax_studiou_wcfpp_delete_files', array($this, 'ajax_delete_files')); add_action('wp_ajax_studiou_wcfpp_download_zip', array($this, 'ajax_download_zip')); add_action('wp_ajax_studiou_wcfpp_download_csv', array($this, 'ajax_download_csv')); add_action('wp_ajax_studiou_wcfpp_bulk_status', array($this, 'ajax_bulk_status')); // Handle direct ZIP / CSV download and file download (marks as downloaded) add_action('admin_init', array($this, 'handle_zip_download')); add_action('admin_init', array($this, 'handle_csv_download')); add_action('admin_init', array($this, 'handle_file_download')); } public function render_summary_page() { // Process filters $current_status = isset($_GET['status']) ? sanitize_text_field($_GET['status']) : ''; $current_product = isset($_GET['product_id']) ? absint($_GET['product_id']) : 0; $current_order = isset($_GET['order_id']) ? absint($_GET['order_id']) : 0; $current_search = isset($_GET['s']) ? sanitize_text_field($_GET['s']) : ''; $current_page = isset($_GET['paged']) ? max(1, absint($_GET['paged'])) : 1; $per_page = 30; $allowed_orderby = array('order_id', 'status', 'created_at'); $orderby_in = isset($_GET['orderby']) ? sanitize_text_field($_GET['orderby']) : 'created_at'; $orderby = in_array($orderby_in, $allowed_orderby, true) ? $orderby_in : 'created_at'; $order_in = isset($_GET['order']) ? strtoupper(sanitize_text_field($_GET['order'])) : 'DESC'; $order = ($order_in === 'ASC') ? 'ASC' : 'DESC'; $args = array( 'status' => $current_status, 'product_id' => $current_product, 'order_id' => $current_order, 'search' => $current_search, 'limit' => $per_page, 'offset' => ($current_page - 1) * $per_page, 'orderby' => $orderby, 'order' => $order, ); $records = $this->db->get_file_records($args); $total = $this->db->count_file_records($args); $total_pages = ceil($total / $per_page); // Get products that have FPP enabled for filter dropdown $fpp_products = $this->get_fpp_products(); // Expose active sort to the view $current_orderby = $orderby; $current_order_dir = $order; include STUDIOU_WCFPP_PLUGIN_DIR . 'views/admin-summary.php'; } private function get_fpp_products() { $args = array( 'post_type' => 'product', 'post_status' => 'publish', 'posts_per_page' => -1, 'meta_query' => array( array( 'key' => '_studiou_fpp_enabled', 'value' => 'yes', ), ), 'fields' => 'ids', ); $product_ids = get_posts($args); $products = array(); foreach ($product_ids as $pid) { $products[$pid] = get_the_title($pid); } return $products; } public function ajax_update_status() { check_ajax_referer('studiou-wcfpp-nonce', 'nonce'); if (!current_user_can('manage_woocommerce')) { wp_send_json_error(array('message' => __('Insufficient permissions.', 'studiou-wc-free-photo-product'))); return; } $file_id = isset($_POST['file_id']) ? absint($_POST['file_id']) : 0; $status = isset($_POST['status']) ? sanitize_text_field($_POST['status']) : ''; if (!$file_id || !$status) { wp_send_json_error(array('message' => __('Invalid data.', 'studiou-wc-free-photo-product'))); return; } $result = $this->db->update_file_status($file_id, $status); if ($result === false) { wp_send_json_error(array('message' => __('Failed to update status.', 'studiou-wc-free-photo-product'))); return; } wp_send_json_success(array('message' => __('Status updated.', 'studiou-wc-free-photo-product'))); } public function ajax_bulk_status() { check_ajax_referer('studiou-wcfpp-nonce', 'nonce'); if (!current_user_can('manage_woocommerce')) { wp_send_json_error(array('message' => __('Insufficient permissions.', 'studiou-wc-free-photo-product'))); return; } $file_ids = isset($_POST['file_ids']) ? array_map('absint', (array) $_POST['file_ids']) : array(); $status = isset($_POST['status']) ? sanitize_text_field($_POST['status']) : ''; if (empty($file_ids) || !$status) { wp_send_json_error(array('message' => __('Invalid data.', 'studiou-wc-free-photo-product'))); return; } $updated = 0; foreach ($file_ids as $fid) { if ($this->db->update_file_status($fid, $status)) { $updated++; } } wp_send_json_success(array( 'message' => sprintf(__('%d file(s) updated.', 'studiou-wc-free-photo-product'), $updated), )); } public function ajax_delete_files() { check_ajax_referer('studiou-wcfpp-nonce', 'nonce'); if (!current_user_can('manage_woocommerce')) { wp_send_json_error(array('message' => __('Insufficient permissions.', 'studiou-wc-free-photo-product'))); return; } $file_ids = isset($_POST['file_ids']) ? array_map('absint', (array) $_POST['file_ids']) : array(); if (empty($file_ids)) { wp_send_json_error(array('message' => __('No files selected.', 'studiou-wc-free-photo-product'))); return; } $deleted = 0; foreach ($file_ids as $fid) { if ($this->db->delete_file_record($fid)) { $deleted++; } } wp_send_json_success(array( 'message' => sprintf(__('%d file(s) deleted.', 'studiou-wc-free-photo-product'), $deleted), )); } public function ajax_download_zip() { check_ajax_referer('studiou-wcfpp-nonce', 'nonce'); if (!current_user_can('manage_woocommerce')) { wp_send_json_error(array('message' => __('Insufficient permissions.', 'studiou-wc-free-photo-product'))); return; } $file_ids = isset($_POST['file_ids']) ? array_map('absint', (array) $_POST['file_ids']) : array(); if (empty($file_ids)) { wp_send_json_error(array('message' => __('No files selected.', 'studiou-wc-free-photo-product'))); return; } $records = $this->db->get_file_records_by_ids($file_ids); if (empty($records)) { wp_send_json_error(array('message' => __('No files found.', 'studiou-wc-free-photo-product'))); return; } // Generate ZIP $upload_dir = wp_upload_dir(); $zip_name = 'fpp-files-' . date('Y-m-d-His') . '.zip'; $zip_path = $upload_dir['basedir'] . '/studiou-fpp-chunks/' . $zip_name; if (!class_exists('ZipArchive')) { wp_send_json_error(array('message' => __('ZIP extension not available on server.', 'studiou-wc-free-photo-product'))); return; } $zip = new ZipArchive(); if ($zip->open($zip_path, ZipArchive::CREATE) !== true) { wp_send_json_error(array('message' => __('Could not create ZIP file.', 'studiou-wc-free-photo-product'))); return; } $added = 0; foreach ($records as $record) { $file_path = wp_get_original_image_path($record->attachment_id); if (!$file_path) { $file_path = get_attached_file($record->attachment_id); } if ($file_path && file_exists($file_path)) { // Use order number prefix if available $prefix = $record->order_id ? 'order-' . $record->order_id . '_' : ''; $zip->addFile($file_path, $prefix . $record->file_name); $added++; } } $zip->close(); if ($added === 0) { if (file_exists($zip_path)) { unlink($zip_path); } wp_send_json_error(array('message' => __('No files could be added to archive.', 'studiou-wc-free-photo-product'))); return; } // Generate a nonce-protected download URL $download_url = add_query_arg(array( 'studiou_fpp_download' => 1, 'file' => $zip_name, '_wpnonce' => wp_create_nonce('studiou-fpp-zip-download'), ), admin_url('admin.php')); wp_send_json_success(array( 'download_url' => $download_url, 'file_count' => $added, )); } public function handle_zip_download() { if (!isset($_GET['studiou_fpp_download']) || !isset($_GET['file'])) { return; } if (!wp_verify_nonce($_GET['_wpnonce'], 'studiou-fpp-zip-download')) { wp_die(__('Security check failed.', 'studiou-wc-free-photo-product')); } if (!current_user_can('manage_woocommerce')) { wp_die(__('Insufficient permissions.', 'studiou-wc-free-photo-product')); } $file_name = sanitize_file_name($_GET['file']); $upload_dir = wp_upload_dir(); $file_path = $upload_dir['basedir'] . '/studiou-fpp-chunks/' . $file_name; if (!file_exists($file_path) || pathinfo($file_name, PATHINFO_EXTENSION) !== 'zip') { wp_die(__('File not found.', 'studiou-wc-free-photo-product')); } header('Content-Type: application/zip'); header('Content-Disposition: attachment; filename="' . $file_name . '"'); header('Content-Length: ' . filesize($file_path)); header('Pragma: no-cache'); readfile($file_path); // Clean up the ZIP file after download unlink($file_path); exit; } public function ajax_download_csv() { check_ajax_referer('studiou-wcfpp-nonce', 'nonce'); if (!current_user_can('manage_woocommerce')) { wp_send_json_error(array('message' => __('Insufficient permissions.', 'studiou-wc-free-photo-product'))); return; } $file_ids = isset($_POST['file_ids']) ? array_map('absint', (array) $_POST['file_ids']) : array(); if (empty($file_ids)) { wp_send_json_error(array('message' => __('No files selected.', 'studiou-wc-free-photo-product'))); return; } $records = $this->db->get_file_records_by_ids($file_ids); if (empty($records)) { wp_send_json_error(array('message' => __('No files found.', 'studiou-wc-free-photo-product'))); return; } $upload_dir = wp_upload_dir(); $csv_name = 'fpp-records-' . date('Y-m-d-His') . '.csv'; $csv_dir = $upload_dir['basedir'] . '/studiou-fpp-chunks'; if (!file_exists($csv_dir)) { wp_mkdir_p($csv_dir); } $csv_path = $csv_dir . '/' . $csv_name; $fh = fopen($csv_path, 'wb'); if (!$fh) { wp_send_json_error(array('message' => __('Could not create CSV file.', 'studiou-wc-free-photo-product'))); return; } // UTF-8 BOM so Excel opens the file with the correct encoding fwrite($fh, "\xEF\xBB\xBF"); // Header row — column labels translated fputcsv($fh, array( __('ID', 'studiou-wc-free-photo-product'), __('File Name', 'studiou-wc-free-photo-product'), __('Product', 'studiou-wc-free-photo-product'), __('Variation ID', 'studiou-wc-free-photo-product'), __('Order', 'studiou-wc-free-photo-product'), __('Customer', 'studiou-wc-free-photo-product'), __('Status', 'studiou-wc-free-photo-product'), __('Date', 'studiou-wc-free-photo-product'), __('File URL', 'studiou-wc-free-photo-product'), )); foreach ($records as $record) { $product_title = get_the_title($record->product_id); if ((int) $record->customer_id > 0) { $user = get_userdata((int) $record->customer_id); $customer_name = $user ? $user->display_name : '#' . (int) $record->customer_id; } else { $customer_name = __('Guest', 'studiou-wc-free-photo-product'); } $file_url = wp_get_attachment_url((int) $record->attachment_id); fputcsv($fh, array( (int) $record->id, (string) $record->file_name, (string) $product_title, (int) $record->variation_id, (int) $record->order_id ? '#' . (int) $record->order_id : '', (string) $customer_name, (string) self::get_status_label($record->status), (string) $record->created_at, (string) ($file_url ?: ''), )); } fclose($fh); $download_url = add_query_arg(array( 'studiou_fpp_download_csv' => 1, 'file' => $csv_name, '_wpnonce' => wp_create_nonce('studiou-fpp-csv-download'), ), admin_url('admin.php')); wp_send_json_success(array( 'download_url' => $download_url, 'row_count' => count($records), )); } public function handle_csv_download() { if (!isset($_GET['studiou_fpp_download_csv']) || !isset($_GET['file'])) { return; } if (!wp_verify_nonce($_GET['_wpnonce'] ?? '', 'studiou-fpp-csv-download')) { wp_die(__('Security check failed.', 'studiou-wc-free-photo-product')); } if (!current_user_can('manage_woocommerce')) { wp_die(__('Insufficient permissions.', 'studiou-wc-free-photo-product')); } $file_name = sanitize_file_name($_GET['file']); $upload_dir = wp_upload_dir(); $file_path = $upload_dir['basedir'] . '/studiou-fpp-chunks/' . $file_name; if (!file_exists($file_path) || pathinfo($file_name, PATHINFO_EXTENSION) !== 'csv') { wp_die(__('File not found.', 'studiou-wc-free-photo-product')); } header('Content-Type: text/csv; charset=UTF-8'); header('Content-Disposition: attachment; filename="' . $file_name . '"'); header('Content-Length: ' . filesize($file_path)); header('Pragma: no-cache'); readfile($file_path); unlink($file_path); exit; } /** * Handle single file download — serves the file and marks status as "downloaded". */ public function handle_file_download() { if (!isset($_GET['studiou_fpp_download_file'])) { return; } if (!wp_verify_nonce($_GET['_wpnonce'] ?? '', 'studiou-fpp-download-file')) { wp_die(__('Security check failed.', 'studiou-wc-free-photo-product')); } if (!current_user_can('manage_woocommerce')) { wp_die(__('Insufficient permissions.', 'studiou-wc-free-photo-product')); } $file_record_id = isset($_GET['file_record_id']) ? absint($_GET['file_record_id']) : 0; $attachment_id = isset($_GET['attachment_id']) ? absint($_GET['attachment_id']) : 0; if (!$attachment_id) { wp_die(__('File not found.', 'studiou-wc-free-photo-product')); } // Mark as downloaded if ($file_record_id) { $record = $this->db->get_file_record($file_record_id); if ($record && $record->status === 'ready') { $this->db->update_file_status($file_record_id, 'downloaded'); } } // Serve the original (unscaled) file — WP auto-scales images above the big-image threshold. $file_path = wp_get_original_image_path($attachment_id); if (!$file_path) { $file_path = get_attached_file($attachment_id); } if (!$file_path || !file_exists($file_path)) { wp_die(__('File not found.', 'studiou-wc-free-photo-product')); } $file_name = basename($file_path); $mime_type = mime_content_type($file_path) ?: 'application/octet-stream'; header('Content-Type: ' . $mime_type); header('Content-Disposition: attachment; filename="' . $file_name . '"'); header('Content-Length: ' . filesize($file_path)); header('Pragma: no-cache'); readfile($file_path); exit; } public static function get_status_label($status) { $labels = array( 'ready' => __('Ready', 'studiou-wc-free-photo-product'), 'downloaded' => __('Downloaded', 'studiou-wc-free-photo-product'), 'solved' => __('Solved', 'studiou-wc-free-photo-product'), ); return isset($labels[$status]) ? $labels[$status] : $status; } public static function get_status_class($status) { $classes = array( 'ready' => 'studiou-fpp-status-ready', 'downloaded' => 'studiou-fpp-status-downloaded', 'solved' => 'studiou-fpp-status-solved', ); return isset($classes[$status]) ? $classes[$status] : ''; } public static function get_order_edit_url($order_id) { if (class_exists('Automattic\WooCommerce\Utilities\OrderUtil') && \Automattic\WooCommerce\Utilities\OrderUtil::custom_orders_table_usage_is_enabled()) { return admin_url('admin.php?page=wc-orders&action=edit&id=' . $order_id); } return admin_url('post.php?post=' . $order_id . '&action=edit'); } }