Changeset 3430317
- Timestamp:
- 01/01/2026 01:49:22 AM (3 months ago)
- Location:
- series-grid/trunk
- Files:
-
- 3 edited
-
CHANGELOG.txt (modified) (1 diff)
-
readme.txt (modified) (2 diffs)
-
series-grid.php (modified) (10 diffs)
Legend:
- Unmodified
- Added
- Removed
-
series-grid/trunk/CHANGELOG.txt
r3387326 r3430317 1 1 === Series Grid Changelog === 2 3 = 4.0.2 = 4 * **Fix:** Licence update 2 5 3 6 = 4.0.1 = -
series-grid/trunk/readme.txt
r3387326 r3430317 5 5 Tested up to: 6.8 6 6 Requires PHP: 7.4 7 Stable tag: 4.0. 17 Stable tag: 4.0.2 8 8 License: GPL2 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 143 143 144 144 == Changelog == 145 146 = 4.0.2 = 147 * **Fix:** Licence update 145 148 146 149 = 4.0.0 = -
series-grid/trunk/series-grid.php
r3387326 r3430317 3 3 Plugin Name: Series Grid 4 4 Description: 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. 15 Version: 4.0.2 6 6 Author: technodrome 7 7 License: GPL2 … … 30 30 31 31 // Plugin constants 32 define('SERIES_GRID_VERSION', '4.0. 0');32 define('SERIES_GRID_VERSION', '4.0.2'); 33 33 34 34 // Define constants only if not already defined by WordPress … … 52 52 add_action('admin_menu', array($this, 'add_admin_menu')); 53 53 add_action('admin_init', array($this, 'register_settings')); 54 add_action('admin_init', array($this, 'check_and_reset_expired')); 54 55 add_shortcode('series_grid', array($this, 'shortcode')); 55 56 … … 71 72 // PRO License System 72 73 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) { 74 78 return self::$is_pro; 75 79 } 76 80 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 78 85 if (empty($license_key)) { 79 86 self::$is_pro = false; … … 95 102 } 96 103 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 */ 97 109 private static function validate_license($license_key) { 98 110 if (strlen($license_key) < 10) { … … 100 112 } 101 113 102 // Demo keys for testing 114 // Demo keys for testing (always valid) - LOCAL FALLBACK 103 115 $demo_keys = [ 104 116 'SGPRO-DEMO-TEST-2025', 105 117 'SGPRO-EVAL-FREE-TRIAL', 106 'SGPRO-BETA-USER-ACCESS' 118 'SGPRO-BETA-USER-ACCESS', 119 'SGPRO-DEV-PERMANENT' 107 120 ]; 108 121 … … 111 124 } 112 125 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) 114 134 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; 115 193 } 116 194 … … 123 201 } 124 202 125 $url = 'https://technodrome.nasrpskom.com/api/ validate-license.php';203 $url = 'https://technodrome.nasrpskom.com/api/licenses/validate-license.php'; 126 204 $domain = isset($_SERVER['HTTP_HOST']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_HOST'])) : 'unknown'; 205 $site_name = get_bloginfo('name'); 127 206 128 207 $response = wp_remote_post($url, [ 129 'timeout' => 1 0,208 'timeout' => 15, 130 209 'body' => [ 131 210 'license_key' => $license_key, 132 211 'domain' => $domain, 133 212 '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' 135 217 ] 136 218 ]); 137 219 138 220 if (is_wp_error($response)) { 139 // Fallback to local validation140 $is_valid =self::local_validation($license_key);221 // Remote API failed - try local validation as fallback 222 return self::local_validation($license_key); 141 223 } else { 142 224 $body = wp_remote_retrieve_body($response); … … 150 232 } 151 233 234 // Local validation fallback for demo keys and component-format keys 152 235 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 163 429 // Enqueue frontend assets 164 430 public function enqueue_frontend_assets() { … … 972 1238 973 1239 // 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; 976 1243 } elseif (isset($current_settings['pro_license_key'])) { 977 1244 $sanitized['pro_license_key'] = $current_settings['pro_license_key']; 978 1245 } 979 1246 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 } 981 1259 982 1260 // Basic settings (always allowed)
Note: See TracChangeset
for help on using the changeset viewer.