Răsfoiți Sursa

Auto-mark downloaded, confirmation dialogs, pagination fixes — v1.1.7 -> v1.2.0

- Download button (order item + photo files view) now serves file
  via admin handler that marks status from ready to downloaded
- All bulk actions have confirmation dialogs (delete, status changes,
  ZIP download)
- Pagination preserves active filters (status, product, search)
- Pagination shows total count
- New admin handler handle_file_download() serves file + updates status
- Order item download uses nonce-protected admin URL instead of direct
  attachment URL
- Add Czech translations for new confirmation messages
- Update documentation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Dalibor Votruba 3 luni în urmă
părinte
comite
3aa900d91c

+ 1 - 1
studiou-wc-free-photo-product/CLAUDE.md

@@ -2,7 +2,7 @@
 
 
 ## Project Overview
 ## Project Overview
 
 
-WordPress/WooCommerce plugin called **studiou-wc-free-photo-product** (QDR - Studiou WC Free Photo Product) v1.1.7.
+WordPress/WooCommerce plugin called **studiou-wc-free-photo-product** (QDR - Studiou WC Free Photo Product) v1.2.0.
 Allows publishing special variable products where customers upload custom raw images to be printed for a price.
 Allows publishing special variable products where customers upload custom raw images to be printed for a price.
 
 
 ## Initial Requirements
 ## Initial Requirements

+ 13 - 3
studiou-wc-free-photo-product/assets/js/admin.js

@@ -152,12 +152,22 @@
                     return;
                     return;
                 }
                 }
 
 
+                var confirmMsg = '';
+                if (action === 'delete') {
+                    confirmMsg = studiouWcfpp.i18n.confirmDelete;
+                } else if (action === 'download_zip') {
+                    confirmMsg = studiouWcfpp.i18n.confirmDownloadZip;
+                } else if (action.startsWith('status_')) {
+                    confirmMsg = studiouWcfpp.i18n.confirmStatusChange;
+                }
+
+                if (confirmMsg && !confirm(confirmMsg)) {
+                    return;
+                }
+
                 if (action === 'download_zip') {
                 if (action === 'download_zip') {
                     bulkDownloadZip(fileIds);
                     bulkDownloadZip(fileIds);
                 } else if (action === 'delete') {
                 } else if (action === 'delete') {
-                    if (!confirm(studiouWcfpp.i18n.confirmDelete)) {
-                        return;
-                    }
                     bulkDelete(fileIds);
                     bulkDelete(fileIds);
                 } else if (action.startsWith('status_')) {
                 } else if (action.startsWith('status_')) {
                     var status = action.replace('status_', '');
                     var status = action.replace('status_', '');

+ 51 - 1
studiou-wc-free-photo-product/includes/class-studiou-wc-fpp-admin.php

@@ -17,8 +17,9 @@ class Studiou_WC_FPP_Admin {
         add_action('wp_ajax_studiou_wcfpp_download_zip', array($this, 'ajax_download_zip'));
         add_action('wp_ajax_studiou_wcfpp_download_zip', array($this, 'ajax_download_zip'));
         add_action('wp_ajax_studiou_wcfpp_bulk_status', array($this, 'ajax_bulk_status'));
         add_action('wp_ajax_studiou_wcfpp_bulk_status', array($this, 'ajax_bulk_status'));
 
 
-        // Handle direct ZIP download
+        // Handle direct ZIP download and file download (marks as downloaded)
         add_action('admin_init', array($this, 'handle_zip_download'));
         add_action('admin_init', array($this, 'handle_zip_download'));
+        add_action('admin_init', array($this, 'handle_file_download'));
     }
     }
 
 
     public function render_summary_page() {
     public function render_summary_page() {
@@ -251,6 +252,55 @@ class Studiou_WC_FPP_Admin {
         exit;
         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 file
+        $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) {
     public static function get_status_label($status) {
         $labels = array(
         $labels = array(
             'ready'      => __('Ready', 'studiou-wc-free-photo-product'),
             'ready'      => __('Ready', 'studiou-wc-free-photo-product'),

+ 11 - 1
studiou-wc-free-photo-product/includes/class-studiou-wc-fpp-cart.php

@@ -259,8 +259,18 @@ class Studiou_WC_FPP_Cart {
             return;
             return;
         }
         }
 
 
+        $file_record_id = $item->get_meta('_studiou_fpp_file_record_id');
+
+        // Build download URL that also marks file as downloaded
+        $download_url = add_query_arg(array(
+            'studiou_fpp_download_file' => 1,
+            'file_record_id' => $file_record_id ?: 0,
+            'attachment_id' => $attachment_id,
+            '_wpnonce' => wp_create_nonce('studiou-fpp-download-file'),
+        ), admin_url('admin.php'));
+
         echo '<p class="studiou-fpp-order-download">';
         echo '<p class="studiou-fpp-order-download">';
-        echo '<a href="' . esc_url($file_url) . '" download class="button button-small">';
+        echo '<a href="' . esc_url($download_url) . '" class="button button-small">';
         echo '<span class="dashicons dashicons-download" style="line-height:1.4;vertical-align:middle;"></span> ';
         echo '<span class="dashicons dashicons-download" style="line-height:1.4;vertical-align:middle;"></span> ';
         echo esc_html__('Download uploaded photo', 'studiou-wc-free-photo-product');
         echo esc_html__('Download uploaded photo', 'studiou-wc-free-photo-product');
         echo '</a>';
         echo '</a>';

+ 7 - 1
studiou-wc-free-photo-product/languages/studiou-wc-free-photo-product-cs_CZ.po

@@ -3,7 +3,7 @@
 # This file is distributed under the GPL v2 or later.
 # This file is distributed under the GPL v2 or later.
 msgid ""
 msgid ""
 msgstr ""
 msgstr ""
-"Project-Id-Version: QDR - Studiou WC Free Photo Product 1.1.7\n"
+"Project-Id-Version: QDR - Studiou WC Free Photo Product 1.2.0\n"
 "Report-Msgid-Bugs-To: https://www.quadarax.com\n"
 "Report-Msgid-Bugs-To: https://www.quadarax.com\n"
 "POT-Creation-Date: 2026-04-02 00:00+0000\n"
 "POT-Creation-Date: 2026-04-02 00:00+0000\n"
 "PO-Revision-Date: 2026-04-02 00:00+0000\n"
 "PO-Revision-Date: 2026-04-02 00:00+0000\n"
@@ -28,6 +28,12 @@ msgstr "Opravdu chcete smazat vybrané soubory?"
 msgid "Are you sure you want to delete this file?"
 msgid "Are you sure you want to delete this file?"
 msgstr "Opravdu chcete smazat tento soubor?"
 msgstr "Opravdu chcete smazat tento soubor?"
 
 
+msgid "Download selected files as ZIP archive?"
+msgstr "Stáhnout vybrané soubory jako ZIP archiv?"
+
+msgid "Are you sure you want to change the status of the selected files?"
+msgstr "Opravdu chcete změnit stav vybraných souborů?"
+
 msgid "No files selected."
 msgid "No files selected."
 msgstr "Nejsou vybrány žádné soubory."
 msgstr "Nejsou vybrány žádné soubory."
 
 

+ 9 - 1
studiou-wc-free-photo-product/languages/studiou-wc-free-photo-product.pot

@@ -2,7 +2,7 @@
 # This file is distributed under the GPL v2 or later.
 # This file is distributed under the GPL v2 or later.
 msgid ""
 msgid ""
 msgstr ""
 msgstr ""
-"Project-Id-Version: QDR - Studiou WC Free Photo Product 1.1.7\n"
+"Project-Id-Version: QDR - Studiou WC Free Photo Product 1.2.0\n"
 "Report-Msgid-Bugs-To: https://www.quadarax.com\n"
 "Report-Msgid-Bugs-To: https://www.quadarax.com\n"
 "POT-Creation-Date: 2026-04-02 00:00+0000\n"
 "POT-Creation-Date: 2026-04-02 00:00+0000\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
@@ -30,6 +30,14 @@ msgstr ""
 msgid "Are you sure you want to delete this file?"
 msgid "Are you sure you want to delete this file?"
 msgstr ""
 msgstr ""
 
 
+#: studiou-wc-free-photo-product.php
+msgid "Download selected files as ZIP archive?"
+msgstr ""
+
+#: studiou-wc-free-photo-product.php
+msgid "Are you sure you want to change the status of the selected files?"
+msgstr ""
+
 #: studiou-wc-free-photo-product.php
 #: studiou-wc-free-photo-product.php
 msgid "No files selected."
 msgid "No files selected."
 msgstr ""
 msgstr ""

+ 6 - 2
studiou-wc-free-photo-product/studiou-wc-free-photo-product.php

@@ -3,7 +3,7 @@
  * Plugin Name: QDR - Studiou WC Free Photo Product
  * Plugin Name: QDR - Studiou WC Free Photo Product
  * Plugin URI: https://www.quadarax.com/plugins/studiou-wc-free-photo-product
  * Plugin URI: https://www.quadarax.com/plugins/studiou-wc-free-photo-product
  * Description: Allows publishing special WooCommerce products with variants and custom raw image upload for photo printing
  * Description: Allows publishing special WooCommerce products with variants and custom raw image upload for photo printing
- * Version: 1.1.7
+ * Version: 1.2.0
  * Requires at least: 6.9.4
  * Requires at least: 6.9.4
  * Requires PHP: 8.2
  * Requires PHP: 8.2
  * Author: Dalibor Votruba
  * Author: Dalibor Votruba
@@ -21,7 +21,7 @@ if (!defined('WPINC')) {
     die;
     die;
 }
 }
 
 
-if (!defined('STUDIOU_WCFPP_VERSION')) define('STUDIOU_WCFPP_VERSION', '1.1.7');
+if (!defined('STUDIOU_WCFPP_VERSION')) define('STUDIOU_WCFPP_VERSION', '1.2.0');
 if (!defined('STUDIOU_WCFPP_PLUGIN_DIR')) define('STUDIOU_WCFPP_PLUGIN_DIR', plugin_dir_path(__FILE__));
 if (!defined('STUDIOU_WCFPP_PLUGIN_DIR')) define('STUDIOU_WCFPP_PLUGIN_DIR', plugin_dir_path(__FILE__));
 if (!defined('STUDIOU_WCFPP_PLUGIN_URL')) define('STUDIOU_WCFPP_PLUGIN_URL', plugin_dir_url(__FILE__));
 if (!defined('STUDIOU_WCFPP_PLUGIN_URL')) define('STUDIOU_WCFPP_PLUGIN_URL', plugin_dir_url(__FILE__));
 if (!defined('STUDIOU_WCFPP_PLUGIN_BASENAME')) define('STUDIOU_WCFPP_PLUGIN_BASENAME', plugin_basename(__FILE__));
 if (!defined('STUDIOU_WCFPP_PLUGIN_BASENAME')) define('STUDIOU_WCFPP_PLUGIN_BASENAME', plugin_basename(__FILE__));
@@ -136,6 +136,8 @@ class Studiou_WC_Free_Photo_Product {
                 'i18n' => array(
                 'i18n' => array(
                     'confirmDelete' => __('Are you sure you want to delete the selected files?', 'studiou-wc-free-photo-product'),
                     'confirmDelete' => __('Are you sure you want to delete the selected files?', 'studiou-wc-free-photo-product'),
                     'confirmDeleteSingle' => __('Are you sure you want to delete this file?', 'studiou-wc-free-photo-product'),
                     'confirmDeleteSingle' => __('Are you sure you want to delete this file?', 'studiou-wc-free-photo-product'),
+                    'confirmDownloadZip' => __('Download selected files as ZIP archive?', 'studiou-wc-free-photo-product'),
+                    'confirmStatusChange' => __('Are you sure you want to change the status of the selected files?', 'studiou-wc-free-photo-product'),
                     'noFilesSelected' => __('No files selected.', 'studiou-wc-free-photo-product'),
                     'noFilesSelected' => __('No files selected.', 'studiou-wc-free-photo-product'),
                     'statusUpdated' => __('Status updated.', 'studiou-wc-free-photo-product'),
                     'statusUpdated' => __('Status updated.', 'studiou-wc-free-photo-product'),
                     'error' => __('An error occurred.', 'studiou-wc-free-photo-product'),
                     'error' => __('An error occurred.', 'studiou-wc-free-photo-product'),
@@ -166,6 +168,8 @@ class Studiou_WC_Free_Photo_Product {
                 'i18n' => array(
                 'i18n' => array(
                     'confirmDelete' => __('Are you sure you want to delete the selected files?', 'studiou-wc-free-photo-product'),
                     'confirmDelete' => __('Are you sure you want to delete the selected files?', 'studiou-wc-free-photo-product'),
                     'confirmDeleteSingle' => __('Are you sure you want to delete this file?', 'studiou-wc-free-photo-product'),
                     'confirmDeleteSingle' => __('Are you sure you want to delete this file?', 'studiou-wc-free-photo-product'),
+                    'confirmDownloadZip' => __('Download selected files as ZIP archive?', 'studiou-wc-free-photo-product'),
+                    'confirmStatusChange' => __('Are you sure you want to change the status of the selected files?', 'studiou-wc-free-photo-product'),
                     'noFilesSelected' => __('No files selected.', 'studiou-wc-free-photo-product'),
                     'noFilesSelected' => __('No files selected.', 'studiou-wc-free-photo-product'),
                     'statusUpdated' => __('Status updated.', 'studiou-wc-free-photo-product'),
                     'statusUpdated' => __('Status updated.', 'studiou-wc-free-photo-product'),
                     'error' => __('An error occurred.', 'studiou-wc-free-photo-product'),
                     'error' => __('An error occurred.', 'studiou-wc-free-photo-product'),

+ 25 - 2
studiou-wc-free-photo-product/views/admin-summary.php

@@ -110,6 +110,14 @@ $base_url = admin_url('edit.php?post_type=product&page=studiou-fpp-summary');
                     } else {
                     } else {
                         $order_link = '<span class="studiou-fpp-no-order">—</span>';
                         $order_link = '<span class="studiou-fpp-no-order">—</span>';
                     }
                     }
+
+                    // Download URL that also marks file as downloaded
+                    $download_url = add_query_arg(array(
+                        'studiou_fpp_download_file' => 1,
+                        'file_record_id' => $record->id,
+                        'attachment_id' => $record->attachment_id,
+                        '_wpnonce' => wp_create_nonce('studiou-fpp-download-file'),
+                    ), admin_url('admin.php'));
                     ?>
                     ?>
                     <tr data-file-id="<?php echo esc_attr($record->id); ?>">
                     <tr data-file-id="<?php echo esc_attr($record->id); ?>">
                         <th scope="row" class="check-column">
                         <th scope="row" class="check-column">
@@ -146,7 +154,7 @@ $base_url = admin_url('edit.php?post_type=product&page=studiou-fpp-summary');
                         </td>
                         </td>
                         <td class="column-actions">
                         <td class="column-actions">
                             <?php if ($attachment_url) : ?>
                             <?php if ($attachment_url) : ?>
-                                <a href="<?php echo esc_url($attachment_url); ?>" class="button button-small" download title="<?php esc_attr_e('Download', 'studiou-wc-free-photo-product'); ?>">
+                                <a href="<?php echo esc_url($download_url); ?>" class="button button-small" title="<?php esc_attr_e('Download', 'studiou-wc-free-photo-product'); ?>">
                                     <span class="dashicons dashicons-download" style="line-height:1.4;"></span>
                                     <span class="dashicons dashicons-download" style="line-height:1.4;"></span>
                                 </a>
                                 </a>
                             <?php endif; ?>
                             <?php endif; ?>
@@ -164,9 +172,24 @@ $base_url = admin_url('edit.php?post_type=product&page=studiou-fpp-summary');
     <?php if ($total_pages > 1) : ?>
     <?php if ($total_pages > 1) : ?>
         <div class="tablenav bottom">
         <div class="tablenav bottom">
             <div class="tablenav-pages">
             <div class="tablenav-pages">
+                <span class="displaying-num">
+                    <?php echo esc_html(sprintf(
+                        _n('%d file', '%d files', $total, 'studiou-wc-free-photo-product'),
+                        $total
+                    )); ?>
+                </span>
                 <?php
                 <?php
+                // Preserve filters in pagination links
+                $pagination_args = array(
+                    'post_type'  => 'product',
+                    'page'       => 'studiou-fpp-summary',
+                );
+                if ($current_status) $pagination_args['status'] = $current_status;
+                if ($current_product) $pagination_args['product_id'] = $current_product;
+                if ($current_search) $pagination_args['s'] = $current_search;
+
                 $pagination = paginate_links(array(
                 $pagination = paginate_links(array(
-                    'base'      => add_query_arg('paged', '%#%'),
+                    'base'      => add_query_arg('paged', '%#%', admin_url('edit.php?' . http_build_query($pagination_args))),
                     'format'    => '',
                     'format'    => '',
                     'current'   => $current_page,
                     'current'   => $current_page,
                     'total'     => $total_pages,
                     'total'     => $total_pages,