Plugin Directory

Changeset 3466559


Ignore:
Timestamp:
02/21/2026 07:03:48 PM (5 weeks ago)
Author:
technodrome
Message:

4.0.3

  • Fix: License validation now correctly reads expires_at field instead of end_date in both validate_component_license() and is_expired() - resolves "PRO license has expired" false positive banner appearing on valid licenses
  • Fix: Stale expiry transient is cleared when valid license is confirmed, preventing cached expired state from blocking PRO features
  • Fix: PRO animations (Fade, Slide, Zoom, Bounce) now trigger on scroll via IntersectionObserver instead of immediately on page load
  • Fix: Zoom and Bounce animation types now display correctly - previously all types used the same slide-up effect regardless of selection
  • Fix: Hover overlay (PRO) now visible on hover - visibility:hidden was set by injected CSS but never unset
  • Fix: Slider arrows no longer scroll double distance - removed redundant event delegation that fired alongside onclick handler in template
  • Fix: Horizontal grid layout (image left/right) now correctly reverts to vertical on mobile - duplicate conflicting CSS rule removed
  • Fix: Touch swipe support now initializes on AJAX-loaded sliders (Load More)
  • Fix: Touch event listeners no longer register multiple times on same slider element after repeated AJAX loads
Location:
series-grid/trunk
Files:
5 edited

Legend:

Unmodified
Added
Removed
  • series-grid/trunk/CHANGELOG.txt

    r3430317 r3466559  
    11=== Series Grid Changelog ===
     2
     3= 4.0.3 =
     4* **Fix:** Moved all inline CSS from PHP to style.css (USTAV compliance)
     5* **Fix:** Removed duplicate instance-success-message HTML element (caused JS bugs)
     6* **Fix:** Removed unnecessary require_once for WordPress core files
     7* **Fix:** Corrected wp_parse_args merge order so instance settings properly override global defaults
     8* **Fix:** Removed debug code left in production (slider debug properties)
     9* **Fix:** Component license validation now checks correct 'expires_at' field (was checking 'end_date'). Fixes Download Counter component keys rejection.
    210
    311= 4.0.2 =
  • series-grid/trunk/assets/css/style.css

    r3387257 r3466559  
    276276   ======================== */
    277277
    278 /* Animation Base - ONLY FOR PRO */
    279 .pro-enabled .series-grid-item,
    280 .pro-enabled .series-list-item,
    281 .pro-enabled .slider-item {
    282   opacity: 0;
    283   animation-duration: 0.6s;
    284   animation-fill-mode: both;
    285 }
    286 
    287 /* Fade Animation */
     278/* Animation Base - items with active animations start hidden, JS adds .is-animated on scroll */
    288279.pro-enabled.animation-fade .series-grid-item,
    289280.pro-enabled.animation-fade .series-list-item,
    290 .pro-enabled.animation-fade .slider-item {
    291   animation-name: fadeInUp;
     281.pro-enabled.animation-fade .slider-item,
     282.pro-enabled.animation-slide .series-grid-item,
     283.pro-enabled.animation-slide .series-list-item,
     284.pro-enabled.animation-slide .slider-item,
     285.pro-enabled.animation-zoom .series-grid-item,
     286.pro-enabled.animation-zoom .series-list-item,
     287.pro-enabled.animation-zoom .slider-item,
     288.pro-enabled.animation-bounce .series-grid-item,
     289.pro-enabled.animation-bounce .series-list-item,
     290.pro-enabled.animation-bounce .slider-item {
     291  opacity: 0;
     292}
     293
     294/* animation-none: override injected CSS - elements immediately visible */
     295.pro-enabled.animation-none .series-grid-item,
     296.pro-enabled.animation-none .series-list-item,
     297.pro-enabled.animation-none .slider-item {
     298  opacity: 1;
     299  animation: none;
     300  transform: none;
     301}
     302
     303/* Fallback: unknown animation type - show when JS adds .is-animated */
     304.pro-enabled .series-grid-item.is-animated,
     305.pro-enabled .series-list-item.is-animated,
     306.pro-enabled .slider-item.is-animated {
     307  opacity: 1;
     308}
     309
     310/* Fade Animation - JS adds .is-animated when item enters viewport */
     311.pro-enabled.animation-fade .series-grid-item.is-animated,
     312.pro-enabled.animation-fade .series-list-item.is-animated,
     313.pro-enabled.animation-fade .slider-item.is-animated {
     314  animation: fadeInUp 0.6s ease both;
    292315}
    293316
     
    303326}
    304327
    305 /* Slide Animation */
    306 .pro-enabled.animation-slide .series-grid-item,
    307 .pro-enabled.animation-slide .series-list-item,
    308 .pro-enabled.animation-slide .slider-item {
    309   animation-name: slideInUp;
     328/* Slide Animation - JS adds .is-animated when item enters viewport */
     329.pro-enabled.animation-slide .series-grid-item.is-animated,
     330.pro-enabled.animation-slide .series-list-item.is-animated,
     331.pro-enabled.animation-slide .slider-item.is-animated {
     332  animation: slideInUp 0.6s ease both;
    310333}
    311334
     
    321344}
    322345
    323 /* Zoom Animation */
    324 .pro-enabled.animation-zoom .series-grid-item,
    325 .pro-enabled.animation-zoom .series-list-item,
    326 .pro-enabled.animation-zoom .slider-item {
    327   animation-name: zoomIn;
     346/* Zoom Animation - JS adds .is-animated when item enters viewport */
     347.pro-enabled.animation-zoom .series-grid-item.is-animated,
     348.pro-enabled.animation-zoom .series-list-item.is-animated,
     349.pro-enabled.animation-zoom .slider-item.is-animated {
     350  animation: zoomIn 0.6s ease both;
    328351}
    329352
     
    339362}
    340363
    341 /* Bounce Animation */
    342 .pro-enabled.animation-bounce .series-grid-item,
    343 .pro-enabled.animation-bounce .series-list-item,
    344 .pro-enabled.animation-bounce .slider-item {
    345   animation-name: bounceIn;
     364/* Bounce Animation - JS adds .is-animated when item enters viewport */
     365.pro-enabled.animation-bounce .series-grid-item.is-animated,
     366.pro-enabled.animation-bounce .series-list-item.is-animated,
     367.pro-enabled.animation-bounce .slider-item.is-animated {
     368  animation: bounceIn 0.6s both;
    346369}
    347370
     
    447470.pro-enabled.hover-overlay .series-grid-item:hover .hover-overlay {
    448471  opacity: 1;
     472  visibility: visible;
    449473}
    450474
     
    842866  .series-grid-item.image-position-left .series-grid-title,
    843867  .series-grid-item.image-position-right .series-grid-title {
    844     margin-top: 15px; /* Vrati marginu */
    845   }
    846 
    847   .series-grid-item.image-position-left .series-grid-image-wrapper,
    848   .series-grid-item.image-position-right .series-grid-image-wrapper {
    849       flex: 0 0 120px;
    850       height: 120px;
     868    margin-top: 15px;
    851869  }
    852870}
     
    893911  }
    894912}
     913
     914/* ============================================================
     915   Admin Settings Page Styles
     916   ============================================================ */
     917
     918/* Tab navigation */
     919.tab-content { display: none; padding-top: 20px; }
     920.tab-content.active { display: block; }
     921.pro-tag { color: #999; font-size: 11px; }
     922
     923/* Instance Manager */
     924.instance-manager {
     925    margin-top: 20px;
     926    background: #fff;
     927    border: 1px solid #ddd;
     928    border-radius: 8px;
     929    padding: 20px;
     930}
     931
     932.instance-list,
     933.instance-create,
     934.instance-quick-reference {
     935    margin-bottom: 25px;
     936}
     937
     938.instance-quick-reference {
     939    background: #f8f9fa;
     940    border: 1px solid #e9ecef;
     941    border-radius: 6px;
     942    padding: 15px;
     943}
     944
     945.instance-quick-reference h4 {
     946    margin-top: 0;
     947    color: #495057;
     948}
     949
     950.shortcodes-list {
     951    margin-top: 15px;
     952}
     953
     954.shortcode-item {
     955    background: white;
     956    border: 1px solid #dee2e6;
     957    border-radius: 4px;
     958    padding: 10px;
     959    margin-bottom: 8px;
     960    display: flex;
     961    align-items: center;
     962}
     963
     964.shortcode-item code {
     965    flex: 1;
     966    font-family: 'Courier New', monospace;
     967    background: #f8f9fa;
     968    padding: 4px 8px;
     969    border-radius: 3px;
     970}
     971
     972.instance-table {
     973    margin-top: 15px;
     974}
     975
     976.instance-search {
     977    margin-bottom: 15px;
     978    display: flex;
     979    align-items: center;
     980}
     981
     982.instance-search input,
     983.instance-search select {
     984    padding: 5px 8px;
     985    border: 1px solid #ccc;
     986    border-radius: 3px;
     987}
     988
     989.layout-badge {
     990    display: inline-block;
     991    padding: 4px 8px;
     992    border-radius: 12px;
     993    font-size: 12px;
     994    font-weight: bold;
     995    text-transform: uppercase;
     996}
     997
     998.layout-grid   { background: #e3f2fd; color: #1976d2; }
     999.layout-list   { background: #f3e5f5; color: #7b1fa2; }
     1000.layout-slider { background: #e8f5e8; color: #388e3c; }
     1001
     1002.action-buttons {
     1003    display: flex;
     1004    gap: 5px;
     1005    flex-wrap: wrap;
     1006}
     1007
     1008.action-buttons .button {
     1009    font-size: 11px;
     1010    padding: 4px 8px;
     1011}
     1012
     1013.instance-create-table {
     1014    background: #f8f9fa;
     1015    border: 1px solid #e9ecef;
     1016    border-radius: 6px;
     1017    padding: 15px;
     1018}
     1019
     1020.loading-indicator {
     1021    color: #0073aa;
     1022}
     1023
     1024.no-instances {
     1025    text-align: center;
     1026    padding: 30px;
     1027    background: #f8f9fa;
     1028    border: 1px solid #e9ecef;
     1029    border-radius: 6px;
     1030}
     1031
     1032/* Instance success message */
     1033.instance-success-message {
     1034    background: #d4edda;
     1035    border: 1px solid #c3e6cb;
     1036    padding: 15px;
     1037    margin-top: 15px;
     1038    border-radius: 4px;
     1039    color: #155724;
     1040}
     1041
     1042.instance-success-message p {
     1043    margin: 0;
     1044}
     1045
     1046.instance-success-message .success-shortcode {
     1047    background: #f8f9fa;
     1048    padding: 5px;
     1049    border-radius: 3px;
     1050    margin-top: 5px;
     1051    display: inline-block;
     1052}
  • series-grid/trunk/assets/js/script.js

    r3387257 r3466559  
    2323            // AJAX load more functionality
    2424            $(document).on('click', '.series-grid-load-more', this.loadMore);
    25            
    26             // Slider navigation
    27             $(document).on('click', '.slider-nav-prev', this.sliderPrev);
    28             $(document).on('click', '.slider-nav-next', this.sliderNext);
    29            
     25
     26            // Note: Slider navigation (.slider-nav-prev/.slider-nav-next) is handled by
     27            // onclick="scrollSlider(...)" in the template - no event delegation needed here
     28
    3029            // Enhanced hover effects
    3130            this.enhanceHoverEffects();
    32            
     31
    3332            // Touch support for mobile
    3433            this.addTouchSupport();
     
    4645                       
    4746                        setTimeout(() => {
    48                             item.style.opacity = '1';
    49                             item.style.transform = 'translateY(0)';
     47                            item.classList.add('is-animated');
    5048                        }, parseInt(delay));
    5149                       
     
    266264        // Touch support for mobile sliders
    267265        addTouchSupport: function() {
    268             const sliders = document.querySelectorAll('.slider-wrapper');
    269            
     266            // Only initialize sliders that haven't been set up yet
     267            const sliders = document.querySelectorAll('.slider-wrapper:not([data-touch-initialized])');
     268
    270269            sliders.forEach(slider => {
     270                slider.setAttribute('data-touch-initialized', 'true');
    271271                let startX, startY, distX, distY;
    272272                let isScrolling = false;
     
    365365        SeriesGridPro.initProAnimations();
    366366        SeriesGridPro.initAutoScroll();
     367        SeriesGridPro.addTouchSupport();
    367368    });
    368369
  • series-grid/trunk/readme.txt

    r3430317 r3466559  
    33Tags: grid, posts, slider, responsive, gallery
    44Requires at least: 5.5
    5 Tested up to: 6.8
     5Tested up to: 6.9
    66Requires PHP: 7.4
    7 Stable tag: 4.0.2
     7Stable tag: 4.0.3
    88License: GPL2
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
  • series-grid/trunk/series-grid.php

    r3430317 r3466559  
    33Plugin Name: Series Grid
    44Description: Display posts in responsive grid, list, or slider layouts with PRO features: animations, color themes, auto-scroll, social sharing, and advanced customization.
    5 Version: 4.0.2
     5Version: 4.0.3
    66Author: technodrome
    77License: GPL2
     
    99Domain Path: /languages
    1010Requires at least: 5.5
    11 Tested up to: 6.8
     11Tested up to: 6.9
    1212Requires PHP: 7.4
    1313*/
     
    1818if (!defined('ABSPATH')) exit;
    1919
    20 // Include WordPress core files
    21 if (!defined('ABSPATH')) {
    22     // ABSPATH should be defined by WordPress, but define it if not available
    23     define('ABSPATH', dirname(__FILE__) . '/');
    24 }
    25 require_once(ABSPATH . 'wp-includes/formatting.php');
    26 require_once(ABSPATH . 'wp-includes/class-wp-query.php');
    27 require_once(ABSPATH . 'wp-includes/pluggable.php');
    28 require_once(ABSPATH . 'wp-includes/functions.php');
    29 require_once(ABSPATH . 'wp-includes/plugin.php');
    30 
    3120// Plugin constants
    32 define('SERIES_GRID_VERSION', '4.0.2');
     21define('SERIES_GRID_VERSION', '4.0.3');
    3322
    3423// Define constants only if not already defined by WordPress
     
    181170        }
    182171       
    183         // Check if license is expired
    184         $end_date = isset($license_data['end_date']) ? $license_data['end_date'] : '';
    185         if (!empty($end_date) && strtotime($end_date) < time()) {
     172        // Check if license is expired (v4.0.3: Check expires_at field, which is the correct one)
     173        // Note: Download Counter component writes 'expires_at', not 'end_date'
     174        $expires_at = isset($license_data['expires_at']) ? $license_data['expires_at'] : '';
     175        if (!empty($expires_at) && strtotime($expires_at) < time()) {
    186176            set_transient($transient_key, 'expired', HOUR_IN_SECONDS);
    187177            return false;
    188178        }
    189179       
    190         // License is valid
     180        // License is valid - clear any stale expiry transient so is_expired() gets fresh data
     181        delete_transient('sg_license_expiry_' . md5($license_key));
    191182        set_transient($transient_key, 'valid', DAY_IN_SECONDS);
    192183        return true;
     
    330321                if (isset($licenses[strtoupper($license_key)])) {
    331322                    $license_data = $licenses[strtoupper($license_key)];
    332                     $end_date = isset($license_data['end_date']) ? $license_data['end_date'] : '';
    333                     if (!empty($end_date)) {
    334                         $expiry_ts = strtotime($end_date);
     323                    // Use expires_at (correct field name) - end_date was the old field name
     324                    $expires_at = isset($license_data['expires_at']) ? $license_data['expires_at'] : '';
     325                    if (!empty($expires_at)) {
     326                        $expiry_ts = strtotime($expires_at);
    335327                        set_transient($transient_key, $expiry_ts, DAY_IN_SECONDS);
    336328                        return $expiry_ts < time();
     
    419411    /**
    420412     * Clear all license-related transients - v4.0.2
     413     * Uses direct database query for bulk transient deletion by pattern.
     414     * No user input is used, so prepared statements are not needed.
    421415     */
    422416    private static function clear_license_transients() {
    423417        global $wpdb;
     418
     419        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    424420        $wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_sg_license_%' OR option_name LIKE '_transient_timeout_sg_license_%'");
     421
     422        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    425423        $wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_series_grid_license_%' OR option_name LIKE '_transient_timeout_series_grid_license_%'");
    426424    }
     
    513511            </form>
    514512        </div>
    515        
    516         <style>
    517             .tab-content { display: none; padding-top: 20px; }
    518             .tab-content.active { display: block; }
    519             .pro-tag { color: #999; font-size: 11px; }
    520         </style>
    521513       
    522514        <?php
     
    14331425        ];
    14341426
    1435         // Merge instance settings with defaults - global settings are defaults, instance settings take priority
    1436         $merged_defaults = wp_parse_args($defaults, $instance_settings);
     1427        // Merge instance settings with defaults - instance settings take priority over global defaults
     1428        $merged_defaults = wp_parse_args($instance_settings, $defaults);
    14371429
    14381430        // Merge with shortcode attributes - shortcode overrides both
     
    15061498            }
    15071499
    1508             // Debug output - remove in production
    1509             if ($atts['layout'] === 'slider') {
    1510                 $post->debug_has_thumbnail = has_post_thumbnail($post->ID) ? 'yes' : 'no';
    1511                 $post->debug_extract_images = $atts['extract_images'];
    1512                 $post->debug_image_found = !empty($post->image) ? 'yes' : 'no';
    1513             }
    1514            
    15151500            // Get category info if needed
    15161501            if ($atts['show_category'] === 'yes') {
     
    17641749            </div>
    17651750
    1766             <!-- Success Message -->
    1767             <div id="instance-success-message" style="display: none; background: #d4edda; border: 1px solid #c3e6cb; padding: 15px; margin-top: 15px; border-radius: 4px;">
    1768                 <p style="margin: 0; color: #155724;">
    1769                     ✅ <strong><?php esc_html_e('Instance Created Successfully!', 'series-grid'); ?></strong><br>
    1770                     <span style="font-size: 14px;"><?php esc_html_e('Use the shortcode below on your frontpage:', 'series-grid'); ?></span>
    1771                     <br>
    1772                     <code id="new-instance-shortcode" style="background: #f8f9fa; padding: 5px; border-radius: 3px; margin-top: 5px; display: inline-block;"></code>
    1773                 </p>
    1774             </div>
    1775 
    17761751            <!-- Instance Quick Reference - Always show this panel -->
    17771752            <div class="instance-quick-reference">
     
    18831858
    18841859            <!-- Success Message -->
    1885             <div id="instance-success-message" style="display: none; background: #d4edda; border: 1px solid #c3e6cb; padding: 15px; margin-top: 15px; border-radius: 4px;">
    1886                 <p style="margin: 0; color: #155724;">
     1860            <div id="instance-success-message" class="instance-success-message" style="display: none;">
     1861                <p>
    18871862                    ✅ <strong><?php esc_html_e('Instance Created Successfully!', 'series-grid'); ?></strong><br>
    1888                     <span style="font-size: 14px;"><?php esc_html_e('Use the shortcode below on your frontpage:', 'series-grid'); ?></span>
     1863                    <span><?php esc_html_e('Use the shortcode below on your frontpage:', 'series-grid'); ?></span>
    18891864                    <br>
    1890                     <code id="new-instance-shortcode" style="background: #f8f9fa; padding: 5px; border-radius: 3px; margin-top: 5px; display: inline-block;"></code>
     1865                    <code id="new-instance-shortcode" class="success-shortcode"></code>
    18911866                </p>
    18921867            </div>
    18931868        </div>
    18941869
    1895         <style>
    1896         .instance-manager {
    1897             margin-top: 20px;
    1898             background: #fff;
    1899             border: 1px solid #ddd;
    1900             border-radius: 8px;
    1901             padding: 20px;
    1902         }
    1903 
    1904         .instance-list, .instance-create, .instance-quick-reference {
    1905             margin-bottom: 25px;
    1906         }
    1907 
    1908         .instance-quick-reference {
    1909             background: #f8f9fa;
    1910             border: 1px solid #e9ecef;
    1911             border-radius: 6px;
    1912             padding: 15px;
    1913         }
    1914 
    1915         .instance-quick-reference h4 {
    1916             margin-top: 0;
    1917             color: #495057;
    1918         }
    1919 
    1920         .shortcodes-list {
    1921             margin-top: 15px;
    1922         }
    1923 
    1924         .shortcode-item {
    1925             background: white;
    1926             border: 1px solid #dee2e6;
    1927             border-radius: 4px;
    1928             padding: 10px;
    1929             margin-bottom: 8px;
    1930             display: flex;
    1931             align-items: center;
    1932         }
    1933 
    1934         .shortcode-item code {
    1935             flex: 1;
    1936             font-family: 'Courier New', monospace;
    1937             background: #f8f9fa;
    1938             padding: 4px 8px;
    1939             border-radius: 3px;
    1940         }
    1941 
    1942         .instance-table {
    1943             margin-top: 15px;
    1944         }
    1945 
    1946         .instance-search {
    1947             margin-bottom: 15px;
    1948             display: flex;
    1949             align-items: center;
    1950         }
    1951 
    1952         .instance-search input, .instance-search select {
    1953             padding: 5px 8px;
    1954             border: 1px solid #ccc;
    1955             border-radius: 3px;
    1956         }
    1957 
    1958         .layout-badge {
    1959             display: inline-block;
    1960             padding: 4px 8px;
    1961             border-radius: 12px;
    1962             font-size: 12px;
    1963             font-weight: bold;
    1964             text-transform: uppercase;
    1965         }
    1966 
    1967         .layout-grid { background: #e3f2fd; color: #1976d2; }
    1968         .layout-list { background: #f3e5f5; color: #7b1fa2; }
    1969         .layout-slider { background: #e8f5e8; color: #388e3c; }
    1970 
    1971         .action-buttons {
    1972             display: flex;
    1973             gap: 5px;
    1974             flex-wrap: wrap;
    1975         }
    1976 
    1977         .action-buttons .button {
    1978             font-size: 11px;
    1979             padding: 4px 8px;
    1980         }
    1981 
    1982         .instance-create-table {
    1983             background: #f8f9fa;
    1984             border: 1px solid #e9ecef;
    1985             border-radius: 6px;
    1986             padding: 15px;
    1987         }
    1988 
    1989         .loading-indicator {
    1990             color: #0073aa;
    1991         }
    1992        
    1993         .no-instances {
    1994             text-align: center;
    1995             padding: 30px;
    1996             background: #f8f9fa;
    1997             border: 1px solid #e9ecef;
    1998             border-radius: 6px;
    1999         }
    2000         </style>
    2001 
    2002         <!-- INLINE JAVASCRIPT REMOVED -->
    20031870        <?php
    20041871    }
Note: See TracChangeset for help on using the changeset viewer.