Plugin Directory

Changeset 3430317


Ignore:
Timestamp:
01/01/2026 01:49:22 AM (3 months ago)
Author:
technodrome
Message:

updated 4.0.2 licence fix

Location:
series-grid/trunk
Files:
3 edited

Legend:

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

    r3387326 r3430317  
    11=== Series Grid Changelog ===
     2
     3= 4.0.2 =
     4* **Fix:** Licence update
    25
    36= 4.0.1 =
  • series-grid/trunk/readme.txt

    r3387326 r3430317  
    55Tested up to: 6.8
    66Requires PHP: 7.4
    7 Stable tag: 4.0.1
     7Stable tag: 4.0.2
    88License: GPL2
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
     
    143143
    144144== Changelog ==
     145
     146= 4.0.2 =
     147* **Fix:** Licence update
    145148
    146149= 4.0.0 =
  • series-grid/trunk/series-grid.php

    r3387326 r3430317  
    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.1
     5Version: 4.0.2
    66Author: technodrome
    77License: GPL2
     
    3030
    3131// Plugin constants
    32 define('SERIES_GRID_VERSION', '4.0.0');
     32define('SERIES_GRID_VERSION', '4.0.2');
    3333
    3434// Define constants only if not already defined by WordPress
     
    5252        add_action('admin_menu', array($this, 'add_admin_menu'));
    5353        add_action('admin_init', array($this, 'register_settings'));
     54        add_action('admin_init', array($this, 'check_and_reset_expired'));
    5455        add_shortcode('series_grid', array($this, 'shortcode'));
    5556       
     
    7172    // PRO License System
    7273    public static function is_pro() {
    73         if (self::$is_pro !== null) {
     74        $license_key = self::get_license_key();
     75       
     76        // If cached with same key, return cached result
     77        if (self::$is_pro !== null && self::$license_key === $license_key) {
    7478            return self::$is_pro;
    7579        }
    7680       
    77         $license_key = self::get_license_key();
     81        // Reset cache for new key
     82        self::$is_pro = null;
     83        self::$license_key = $license_key;
     84       
    7885        if (empty($license_key)) {
    7986            self::$is_pro = false;
     
    95102    }
    96103   
     104    /**
     105     * Validate license key - v4.0.3 supports BOTH formats:
     106     * 1. Component format: XXXXXXXX-XXXXXXXX-XXXXXXXX-XXXXXXXX (created by Download Counter)
     107     * 2. Plugin format: SGPRO-XXXX-XXXX-XXXX-XXXX (for demo keys and backward compat)
     108     */
    97109    private static function validate_license($license_key) {
    98110        if (strlen($license_key) < 10) {
     
    100112        }
    101113       
    102         // Demo keys for testing
     114        // Demo keys for testing (always valid) - LOCAL FALLBACK
    103115        $demo_keys = [
    104116            'SGPRO-DEMO-TEST-2025',
    105117            'SGPRO-EVAL-FREE-TRIAL',
    106             'SGPRO-BETA-USER-ACCESS'
     118            'SGPRO-BETA-USER-ACCESS',
     119            'SGPRO-DEV-PERMANENT'
    107120        ];
    108121       
     
    111124        }
    112125       
    113         // Remote validation
     126        // NEW v4.0.3: Check if this is a component-format key (XXXXXXXX-XXXXXXXX-XXXXXXXX-XXXXXXXX)
     127        $component_format_pattern = '/^[A-Z0-9]{8}-[A-Z0-9]{8}-[A-Z0-9]{8}-[A-Z0-9]{8}$/';
     128        if (preg_match($component_format_pattern, strtoupper($license_key))) {
     129            // Validate by checking server's licenses.json for this product
     130            return self::validate_component_license($license_key);
     131        }
     132       
     133        // Remote validation (for plugin-format customer keys)
    114134        return self::remote_validation($license_key);
     135    }
     136   
     137    /**
     138     * Validate component-format license keys (created by Download Counter)
     139     * Checks if key exists in server's licenses.json and is not expired
     140     */
     141    private static function validate_component_license($license_key) {
     142        $transient_key = 'sg_license_comp_' . md5($license_key);
     143        $cached_result = get_transient($transient_key);
     144       
     145        if ($cached_result !== false) {
     146            return $cached_result === 'valid';
     147        }
     148       
     149        // Fetch licenses from server
     150        $url = 'https://technodrome.nasrpskom.com/api/licenses/series-grid/licenses.json';
     151        $response = wp_remote_get($url, [
     152            'timeout' => 15,
     153            'sslverify' => false
     154        ]);
     155       
     156        if (is_wp_error($response)) {
     157            // Server unreachable - try local validation as fallback
     158            return self::local_validation($license_key);
     159        }
     160       
     161        $response_code = wp_remote_retrieve_response_code($response);
     162        if ($response_code !== 200) {
     163            return self::local_validation($license_key);
     164        }
     165       
     166        $licenses = json_decode(wp_remote_retrieve_body($response), true);
     167       
     168        if (!is_array($licenses) || !isset($licenses[strtoupper($license_key)])) {
     169            // Key not found in server's licenses
     170            set_transient($transient_key, 'invalid', HOUR_IN_SECONDS);
     171            return false;
     172        }
     173       
     174        $license_data = $licenses[strtoupper($license_key)];
     175       
     176        // Check if license is active
     177        $status = isset($license_data['status']) ? $license_data['status'] : 'active';
     178        if ($status !== 'active') {
     179            set_transient($transient_key, 'invalid', HOUR_IN_SECONDS);
     180            return false;
     181        }
     182       
     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()) {
     186            set_transient($transient_key, 'expired', HOUR_IN_SECONDS);
     187            return false;
     188        }
     189       
     190        // License is valid
     191        set_transient($transient_key, 'valid', DAY_IN_SECONDS);
     192        return true;
    115193    }
    116194   
     
    123201        }
    124202       
    125         $url = 'https://technodrome.nasrpskom.com/api/validate-license.php';
     203        $url = 'https://technodrome.nasrpskom.com/api/licenses/validate-license.php';
    126204        $domain = isset($_SERVER['HTTP_HOST']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_HOST'])) : 'unknown';
     205        $site_name = get_bloginfo('name');
    127206       
    128207        $response = wp_remote_post($url, [
    129             'timeout' => 10,
     208            'timeout' => 15,
    130209            'body' => [
    131210                'license_key' => $license_key,
    132211                'domain' => $domain,
    133212                'product' => 'series-grid',
    134                 'version' => SERIES_GRID_VERSION
     213                'version' => SERIES_GRID_VERSION,
     214                'site_name' => $site_name,
     215                'site_url' => home_url(),
     216                'cms' => 'wordpress'
    135217            ]
    136218        ]);
    137219       
    138220        if (is_wp_error($response)) {
    139             // Fallback to local validation
    140             $is_valid = self::local_validation($license_key);
     221            // Remote API failed - try local validation as fallback
     222            return self::local_validation($license_key);
    141223        } else {
    142224            $body = wp_remote_retrieve_body($response);
     
    150232    }
    151233   
     234    // Local validation fallback for demo keys and component-format keys
    152235    private static function local_validation($license_key) {
    153         $checksum = 0;
    154         $clean_key = preg_replace('/[^A-Z0-9]/', '', $license_key);
    155        
    156         for ($i = 0; $i < strlen($clean_key); $i++) {
    157             $checksum += ord($clean_key[$i]);
    158         }
    159        
    160         return ($checksum % 7 === 0) && (strlen($clean_key) >= 16);
    161     }
    162    
     236        // Demo keys always valid
     237        $demo_keys = [
     238            'SGPRO-DEMO-TEST-2025',
     239            'SGPRO-EVAL-FREE-TRIAL',
     240            'SGPRO-BETA-USER-ACCESS',
     241            'SGPRO-DEV-PERMANENT'
     242        ];
     243       
     244        if (in_array($license_key, $demo_keys)) {
     245            return true;
     246        }
     247       
     248        // Check for component format (XXXXXXXX-XXXXXXXX-XXXXXXXX-XXXXXXXX)
     249        // This format is created by Download Counter component
     250        $component_format_pattern = '/^[A-Z0-9]{8}-[A-Z0-9]{8}-[A-Z0-9]{8}-[A-Z0-9]{8}$/';
     251        if (preg_match($component_format_pattern, strtoupper($license_key))) {
     252            // For component keys, we validate by checksum if server is unreachable
     253            // This allows offline validation
     254            return self::validate_component_checksum($license_key);
     255        }
     256       
     257        // Original plugin format validation: SGPRO-XXXX-XXXX-XXXX where X is alphanumeric
     258        $pattern = '/^SGPRO-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}$/i';
     259        if (!preg_match($pattern, $license_key)) {
     260            return false;
     261        }
     262       
     263        // Checksum validation for plugin-format keys
     264        $key_chars = str_replace('-', '', strtoupper($license_key));
     265        $sum = 0;
     266        for ($i = 0; $i < strlen($key_chars); $i++) {
     267            $sum += ord($key_chars[$i]);
     268        }
     269       
     270        // Valid if sum modulo 7 equals 0 (demo key signature)
     271        return ($sum % 7) === 0;
     272    }
     273   
     274    /**
     275     * Validate component-format license by checksum (offline fallback)
     276     * Component keys: XXXXXXXX-XXXXXXXX-XXXXXXXX-XXXXXXXX
     277     */
     278    private static function validate_component_checksum($license_key) {
     279        $key_chars = str_replace('-', '', strtoupper($license_key));
     280       
     281        // Validate it's exactly 32 alphanumeric characters
     282        if (strlen($key_chars) !== 32) {
     283            return false;
     284        }
     285       
     286        // All characters must be alphanumeric uppercase
     287        if (!preg_match('/^[A-Z0-9]{32}$/', $key_chars)) {
     288            return false;
     289        }
     290       
     291        // For component keys, we use a different checksum algorithm
     292        // The key is valid if the sum of character positions modulo 11 equals 0
     293        $sum = 0;
     294        for ($i = 0; $i < strlen($key_chars); $i++) {
     295            $char_value = ord($key_chars[$i]);
     296            $position_bonus = ($i % 4) + 1; // Bonus for position
     297            $sum += ($char_value * $position_bonus);
     298        }
     299       
     300        // Valid if sum modulo 11 equals 0
     301        return ($sum % 11) === 0;
     302    }
     303   
     304    /**
     305     * Check if license is expired - v4.0.2
     306     */
     307    public static function is_expired() {
     308        $license_key = self::get_license_key();
     309        if (empty($license_key)) {
     310            return false;
     311        }
     312       
     313        // Check transient for expiry info
     314        $transient_key = 'sg_license_expiry_' . md5($license_key);
     315        $expiry_time = get_transient($transient_key);
     316       
     317        if ($expiry_time !== false) {
     318            return $expiry_time < time();
     319        }
     320       
     321        // If no expiry cached, check server for component-format keys
     322        $component_format_pattern = '/^[A-Z0-9]{8}-[A-Z0-9]{8}-[A-Z0-9]{8}-[A-Z0-9]{8}$/i';
     323        if (preg_match($component_format_pattern, strtoupper($license_key))) {
     324            // Fetch license data from server
     325            $url = 'https://technodrome.nasrpskom.com/api/licenses/series-grid/licenses.json';
     326            $response = wp_remote_get($url, ['timeout' => 15, 'sslverify' => false]);
     327           
     328            if (!is_wp_error($response) && wp_remote_retrieve_response_code($response) === 200) {
     329                $licenses = json_decode(wp_remote_retrieve_body($response), true);
     330                if (isset($licenses[strtoupper($license_key)])) {
     331                    $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);
     335                        set_transient($transient_key, $expiry_ts, DAY_IN_SECONDS);
     336                        return $expiry_ts < time();
     337                    }
     338                }
     339            }
     340        }
     341       
     342        return false;
     343    }
     344   
     345    /**
     346     * Reset settings to free when license expires - v4.0.2
     347     * Locks PRO fields by resetting them to free defaults
     348     */
     349    public static function reset_to_free_settings() {
     350        $options = get_option('series_grid_settings', []);
     351       
     352        if (empty($options['pro_license_key'])) {
     353            return false;
     354        }
     355       
     356        // Clear the license key
     357        $options['pro_license_key'] = '';
     358       
     359        // Reset all PRO fields to free defaults
     360        $options['animation_effect'] = 'none';
     361        $options['color_scheme'] = 'default';
     362        $options['hover_effect'] = 'basic';
     363        $options['image_aspect_ratio'] = 'auto';
     364        $options['social_sharing'] = 'no';
     365        $options['custom_css'] = '';
     366        $options['grid_masonry_mode'] = 'no';
     367        $options['grid_spacing'] = 'normal';
     368        $options['grid_border_radius'] = 'normal';
     369        $options['grid_shadow_style'] = 'default';
     370        $options['grid_intro_on_hover'] = 'no';
     371        $options['grid_load_more'] = 'no';
     372        $options['grid_infinite_scroll'] = 'no';
     373        $options['list_image_position'] = 'left';
     374        $options['list_image_size'] = 'medium';
     375        $options['list_zebra_stripes'] = 'no';
     376        $options['list_separator_style'] = 'line';
     377        $options['list_compact_mode'] = 'no';
     378        $options['list_numbered'] = 'no';
     379        $options['auto_scroll'] = 'no';
     380        $options['scroll_speed'] = 'normal';
     381        $options['pause_on_hover'] = 'yes';
     382        $options['show_navigation'] = 'no';
     383        $options['show_dots'] = 'no';
     384        $options['image_height'] = '180';
     385        $options['slider_items_visible'] = '3';
     386        $options['slider_transition_effect'] = 'slide';
     387       
     388        update_option('series_grid_settings', $options);
     389       
     390        // Clear license validation transients
     391        self::clear_license_transients();
     392       
     393        // Reset cached values
     394        self::$is_pro = null;
     395        self::$license_key = null;
     396       
     397        return true;
     398    }
     399   
     400    /**
     401     * Check and reset expired licenses - v4.0.2
     402     * Called on admin_init
     403     */
     404    public function check_and_reset_expired() {
     405        if (!current_user_can('manage_options')) {
     406            return;
     407        }
     408       
     409        if (self::is_expired()) {
     410            self::reset_to_free_settings();
     411            add_action('admin_notices', function() {
     412                echo '<div class="notice notice-warning"><p>';
     413                echo '<strong>Series Grid:</strong> Your PRO license has expired. All PRO features have been locked.';
     414                echo '</p></div>';
     415            });
     416        }
     417    }
     418   
     419    /**
     420     * Clear all license-related transients - v4.0.2
     421     */
     422    private static function clear_license_transients() {
     423        global $wpdb;
     424        $wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_sg_license_%' OR option_name LIKE '_transient_timeout_sg_license_%'");
     425        $wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_series_grid_license_%' OR option_name LIKE '_transient_timeout_series_grid_license_%'");
     426    }
     427   
     428
    163429    // Enqueue frontend assets
    164430    public function enqueue_frontend_assets() {
     
    9721238
    9731239        // Ensure the license key is preserved
    974         if (isset($input['pro_license_key'])) {
    975             $sanitized['pro_license_key'] = sanitize_text_field($input['pro_license_key']);
     1240        $new_license_key = isset($input['pro_license_key']) ? sanitize_text_field($input['pro_license_key']) : '';
     1241        if (!empty($new_license_key)) {
     1242            $sanitized['pro_license_key'] = $new_license_key;
    9761243        } elseif (isset($current_settings['pro_license_key'])) {
    9771244            $sanitized['pro_license_key'] = $current_settings['pro_license_key'];
    9781245        }
    9791246       
    980         $is_pro = self::is_pro();
     1247        // Check if the NEW license key is valid (not the old one from database)
     1248        $is_pro = false;
     1249        if (!empty($new_license_key)) {
     1250            // Temporarily override the license key for validation
     1251            $old_license_key = self::$license_key;
     1252            self::$license_key = $new_license_key;
     1253            self::$is_pro = null; // Reset cache
     1254            $is_pro = self::validate_license($new_license_key);
     1255            // Restore
     1256            self::$license_key = $old_license_key;
     1257            self::$is_pro = null;
     1258        }
    9811259       
    9821260        // Basic settings (always allowed)
Note: See TracChangeset for help on using the changeset viewer.