Răsfoiți Sursa

Add min/max image resolution validation — v1.2.0 -> v1.2.1

- Add Min Image Resolution and Max Image Resolution fields to Free
  Photo product tab (width x height in pixels)
- Width and height are commutable: a 3000x2000 image passes both
  3000x2000 and 2000x3000 limits (portrait/landscape auto-detected)
- Validated server-side after file assembly, before attachment creation
- RAW files where getimagesize() fails are skipped (no validation)
- Empty/zero values mean no limit
- Add Czech translations for all new strings
- Update documentation and product meta keys table

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

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

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

+ 45 - 0
studiou-wc-free-photo-product/includes/class-studiou-wc-fpp-product.php

@@ -32,6 +32,10 @@ class Studiou_WC_FPP_Product {
         $enabled = get_post_meta($product_id, '_studiou_fpp_enabled', true);
         $media_category = get_post_meta($product_id, '_studiou_fpp_media_category', true);
         $max_file_size = get_post_meta($product_id, '_studiou_fpp_max_file_size', true);
+        $min_width = get_post_meta($product_id, '_studiou_fpp_min_width', true);
+        $min_height = get_post_meta($product_id, '_studiou_fpp_min_height', true);
+        $max_width = get_post_meta($product_id, '_studiou_fpp_max_width', true);
+        $max_height = get_post_meta($product_id, '_studiou_fpp_max_height', true);
 
         if (empty($max_file_size)) {
             $max_file_size = 50;
@@ -100,6 +104,27 @@ class Studiou_WC_FPP_Product {
                 ?>
             </div>
 
+            <div class="options_group">
+                <p class="form-field">
+                    <label><?php esc_html_e('Min Image Resolution (px)', 'studiou-wc-free-photo-product'); ?></label>
+                    <span class="wrap">
+                        <input type="number" id="_studiou_fpp_min_width" name="_studiou_fpp_min_width" class="short" style="width:80px;" min="0" step="1" value="<?php echo esc_attr($min_width); ?>" placeholder="<?php esc_attr_e('Width', 'studiou-wc-free-photo-product'); ?>" />
+                        &times;
+                        <input type="number" id="_studiou_fpp_min_height" name="_studiou_fpp_min_height" class="short" style="width:80px;" min="0" step="1" value="<?php echo esc_attr($min_height); ?>" placeholder="<?php esc_attr_e('Height', 'studiou-wc-free-photo-product'); ?>" />
+                    </span>
+                    <?php echo wc_help_tip(__('Minimum image resolution in pixels. Leave empty to skip. Width and height are interchangeable (portrait/landscape auto-detected).', 'studiou-wc-free-photo-product')); ?>
+                </p>
+                <p class="form-field">
+                    <label><?php esc_html_e('Max Image Resolution (px)', 'studiou-wc-free-photo-product'); ?></label>
+                    <span class="wrap">
+                        <input type="number" id="_studiou_fpp_max_width" name="_studiou_fpp_max_width" class="short" style="width:80px;" min="0" step="1" value="<?php echo esc_attr($max_width); ?>" placeholder="<?php esc_attr_e('Width', 'studiou-wc-free-photo-product'); ?>" />
+                        &times;
+                        <input type="number" id="_studiou_fpp_max_height" name="_studiou_fpp_max_height" class="short" style="width:80px;" min="0" step="1" value="<?php echo esc_attr($max_height); ?>" placeholder="<?php esc_attr_e('Height', 'studiou-wc-free-photo-product'); ?>" />
+                    </span>
+                    <?php echo wc_help_tip(__('Maximum image resolution in pixels. Leave empty to skip. Width and height are interchangeable (portrait/landscape auto-detected).', 'studiou-wc-free-photo-product')); ?>
+                </p>
+            </div>
+
             <div class="options_group">
                 <p class="form-field">
                     <label><?php esc_html_e('Shortcode', 'studiou-wc-free-photo-product'); ?></label>
@@ -125,6 +150,13 @@ class Studiou_WC_FPP_Product {
             if ($max_size > 500) $max_size = 500;
             update_post_meta($product_id, '_studiou_fpp_max_file_size', $max_size);
         }
+
+        // Resolution limits (0 or empty = no limit)
+        $res_fields = array('_studiou_fpp_min_width', '_studiou_fpp_min_height', '_studiou_fpp_max_width', '_studiou_fpp_max_height');
+        foreach ($res_fields as $field) {
+            $val = isset($_POST[$field]) ? absint($_POST[$field]) : 0;
+            update_post_meta($product_id, $field, $val ?: '');
+        }
     }
 
     public function ajax_add_media_category() {
@@ -165,4 +197,17 @@ class Studiou_WC_FPP_Product {
     public static function get_product_media_category($product_id) {
         return (int) get_post_meta($product_id, '_studiou_fpp_media_category', true);
     }
+
+    /**
+     * Get resolution limits for a product.
+     * Returns array with min_width, min_height, max_width, max_height (0 = no limit).
+     */
+    public static function get_product_resolution_limits($product_id) {
+        return array(
+            'min_width'  => (int) get_post_meta($product_id, '_studiou_fpp_min_width', true),
+            'min_height' => (int) get_post_meta($product_id, '_studiou_fpp_min_height', true),
+            'max_width'  => (int) get_post_meta($product_id, '_studiou_fpp_max_width', true),
+            'max_height' => (int) get_post_meta($product_id, '_studiou_fpp_max_height', true),
+        );
+    }
 }

+ 68 - 0
studiou-wc-free-photo-product/includes/class-studiou-wc-fpp-upload.php

@@ -189,6 +189,13 @@ class Studiou_WC_FPP_Upload {
             return new WP_Error('invalid_type', __('File type not allowed.', 'studiou-wc-free-photo-product'));
         }
 
+        // Validate image resolution (if limits are set)
+        $res_error = $this->validate_image_resolution($assembled_path, $product_id);
+        if (is_wp_error($res_error)) {
+            unlink($assembled_path);
+            return $res_error;
+        }
+
         // Create WP attachment
         $relative_path = str_replace($upload_dir['basedir'] . '/', '', $assembled_path);
         $filetype = wp_check_filetype($unique_name, $allowed);
@@ -278,6 +285,67 @@ class Studiou_WC_FPP_Upload {
         );
     }
 
+    /**
+     * Validate image resolution against product limits.
+     * Width/height are commutable — a 3000x2000 image matches both 3000x2000 and 2000x3000 limits.
+     */
+    private function validate_image_resolution($file_path, $product_id) {
+        $limits = Studiou_WC_FPP_Product::get_product_resolution_limits($product_id);
+
+        // Skip if no limits set
+        $has_min = ($limits['min_width'] > 0 || $limits['min_height'] > 0);
+        $has_max = ($limits['max_width'] > 0 || $limits['max_height'] > 0);
+        if (!$has_min && !$has_max) {
+            return true;
+        }
+
+        // Get image dimensions
+        $size = @getimagesize($file_path);
+        if (!$size || !isset($size[0], $size[1])) {
+            // Cannot determine dimensions (e.g. RAW files) — skip validation
+            return true;
+        }
+
+        $img_w = $size[0];
+        $img_h = $size[1];
+
+        // Normalize: always compare min(w,h) vs min(limit_w,limit_h) and max(w,h) vs max(limit_w,limit_h)
+        // This makes portrait/landscape interchangeable
+        $img_short = min($img_w, $img_h);
+        $img_long = max($img_w, $img_h);
+
+        // Minimum resolution check
+        if ($has_min) {
+            $min_short = min($limits['min_width'] ?: 0, $limits['min_height'] ?: 0);
+            $min_long = max($limits['min_width'] ?: 0, $limits['min_height'] ?: 0);
+            // If only one dimension is set, use it for both
+            if ($min_short === 0) $min_short = $min_long;
+
+            if ($img_short < $min_short || $img_long < $min_long) {
+                return new WP_Error('resolution_too_small', sprintf(
+                    __('Image resolution %1$dx%2$d px is too small. Minimum required: %3$dx%4$d px.', 'studiou-wc-free-photo-product'),
+                    $img_w, $img_h, $limits['min_width'] ?: $limits['min_height'], $limits['min_height'] ?: $limits['min_width']
+                ));
+            }
+        }
+
+        // Maximum resolution check
+        if ($has_max) {
+            $max_short = min($limits['max_width'] ?: PHP_INT_MAX, $limits['max_height'] ?: PHP_INT_MAX);
+            $max_long = max($limits['max_width'] ?: PHP_INT_MAX, $limits['max_height'] ?: PHP_INT_MAX);
+            if ($max_short === PHP_INT_MAX) $max_short = $max_long;
+
+            if ($img_short > $max_short || $img_long > $max_long) {
+                return new WP_Error('resolution_too_large', sprintf(
+                    __('Image resolution %1$dx%2$d px is too large. Maximum allowed: %3$dx%4$d px.', 'studiou-wc-free-photo-product'),
+                    $img_w, $img_h, $limits['max_width'] ?: $limits['max_height'], $limits['max_height'] ?: $limits['max_width']
+                ));
+            }
+        }
+
+        return true;
+    }
+
     private function cleanup_chunks($upload_id, $total_chunks) {
         $chunks_dir = $this->get_chunks_dir();
         for ($i = 0; $i < $total_chunks; $i++) {

+ 25 - 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.
 msgid ""
 msgstr ""
-"Project-Id-Version: QDR - Studiou WC Free Photo Product 1.2.0\n"
+"Project-Id-Version: QDR - Studiou WC Free Photo Product 1.2.1\n"
 "Report-Msgid-Bugs-To: https://www.quadarax.com\n"
 "POT-Creation-Date: 2026-04-02 00:00+0000\n"
 "PO-Revision-Date: 2026-04-02 00:00+0000\n"
@@ -148,6 +148,30 @@ msgstr "Shortcode"
 msgid "Use this shortcode to display this product with photo upload on any page."
 msgstr "Použijte tento shortcode pro zobrazení tohoto produktu s nahráváním fotografií na jakékoli stránce."
 
+msgid "Min Image Resolution (px)"
+msgstr "Min. rozlišení obrázku (px)"
+
+msgid "Width"
+msgstr "Šířka"
+
+msgid "Height"
+msgstr "Výška"
+
+msgid "Minimum image resolution in pixels. Leave empty to skip. Width and height are interchangeable (portrait/landscape auto-detected)."
+msgstr "Minimální rozlišení obrázku v pixelech. Ponechte prázdné pro přeskočení. Šířka a výška jsou zaměnitelné (automatická detekce orientace)."
+
+msgid "Max Image Resolution (px)"
+msgstr "Max. rozlišení obrázku (px)"
+
+msgid "Maximum image resolution in pixels. Leave empty to skip. Width and height are interchangeable (portrait/landscape auto-detected)."
+msgstr "Maximální rozlišení obrázku v pixelech. Ponechte prázdné pro přeskočení. Šířka a výška jsou zaměnitelné (automatická detekce orientace)."
+
+msgid "Image resolution %1$dx%2$d px is too small. Minimum required: %3$dx%4$d px."
+msgstr "Rozlišení obrázku %1$dx%2$d px je příliš malé. Minimální požadované: %3$dx%4$d px."
+
+msgid "Image resolution %1$dx%2$d px is too large. Maximum allowed: %3$dx%4$d px."
+msgstr "Rozlišení obrázku %1$dx%2$d px je příliš velké. Maximální povolené: %3$dx%4$d px."
+
 msgid "Category name is required."
 msgstr "Název kategorie je povinný."
 

+ 35 - 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.
 msgid ""
 msgstr ""
-"Project-Id-Version: QDR - Studiou WC Free Photo Product 1.2.0\n"
+"Project-Id-Version: QDR - Studiou WC Free Photo Product 1.2.1\n"
 "Report-Msgid-Bugs-To: https://www.quadarax.com\n"
 "POT-Creation-Date: 2026-04-02 00:00+0000\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
@@ -190,6 +190,40 @@ msgstr ""
 msgid "Use this shortcode to display this product with photo upload on any page."
 msgstr ""
 
+#: includes/class-studiou-wc-fpp-product.php
+msgid "Min Image Resolution (px)"
+msgstr ""
+
+#: includes/class-studiou-wc-fpp-product.php
+msgid "Width"
+msgstr ""
+
+#: includes/class-studiou-wc-fpp-product.php
+msgid "Height"
+msgstr ""
+
+#: includes/class-studiou-wc-fpp-product.php
+msgid "Minimum image resolution in pixels. Leave empty to skip. Width and height are interchangeable (portrait/landscape auto-detected)."
+msgstr ""
+
+#: includes/class-studiou-wc-fpp-product.php
+msgid "Max Image Resolution (px)"
+msgstr ""
+
+#: includes/class-studiou-wc-fpp-product.php
+msgid "Maximum image resolution in pixels. Leave empty to skip. Width and height are interchangeable (portrait/landscape auto-detected)."
+msgstr ""
+
+#: includes/class-studiou-wc-fpp-upload.php
+#, php-format
+msgid "Image resolution %1$dx%2$d px is too small. Minimum required: %3$dx%4$d px."
+msgstr ""
+
+#: includes/class-studiou-wc-fpp-upload.php
+#, php-format
+msgid "Image resolution %1$dx%2$d px is too large. Maximum allowed: %3$dx%4$d px."
+msgstr ""
+
 #: includes/class-studiou-wc-fpp-product.php
 msgid "Category name is required."
 msgstr ""

+ 6 - 1
studiou-wc-free-photo-product/readme.md

@@ -25,7 +25,8 @@ WordPress (6.9.4+) / WooCommerce (10.6.2+) plugin that allows publishing special
 4. Check **Enable Free Photo**
 5. Select or create a **Media Category** - uploaded files will be assigned to this category
 6. Set **Max File Size (MB)** - maximum allowed upload size per file (1-500 MB, default 50)
-7. Copy the displayed **Shortcode** for use on custom pages
+7. Optionally set **Min Image Resolution (px)** and **Max Image Resolution (px)** - width and height are interchangeable (portrait/landscape auto-detected). Leave empty to skip validation. RAW files that cannot be measured are skipped.
+8. Copy the displayed **Shortcode** for use on custom pages
 
 ### Product Detail Page
 
@@ -231,6 +232,10 @@ studiou-wc-free-photo-product/
 | `_studiou_fpp_enabled` | "yes" / "no" |
 | `_studiou_fpp_media_category` | Term ID of media category |
 | `_studiou_fpp_max_file_size` | Max upload size in MB |
+| `_studiou_fpp_min_width` | Min image width in px (0 = no limit) |
+| `_studiou_fpp_min_height` | Min image height in px (0 = no limit) |
+| `_studiou_fpp_max_width` | Max image width in px (0 = no limit) |
+| `_studiou_fpp_max_height` | Max image height in px (0 = no limit) |
 
 ### Order Item Meta Keys
 

+ 2 - 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 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
- * Version: 1.2.0
+ * Version: 1.2.1
  * Requires at least: 6.9.4
  * Requires PHP: 8.2
  * Author: Dalibor Votruba
@@ -21,7 +21,7 @@ if (!defined('WPINC')) {
     die;
 }
 
-if (!defined('STUDIOU_WCFPP_VERSION')) define('STUDIOU_WCFPP_VERSION', '1.2.0');
+if (!defined('STUDIOU_WCFPP_VERSION')) define('STUDIOU_WCFPP_VERSION', '1.2.1');
 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_BASENAME')) define('STUDIOU_WCFPP_PLUGIN_BASENAME', plugin_basename(__FILE__));