PluginProbe
Ultimate Store Kit – Store Builder Addons for Elementor, WooCommerce Store Builder, EDD Store Builder / 3.1.4
Ultimate Store Kit – Store Builder Addons for Elementor, WooCommerce Store Builder, EDD Store Builder v3.1.4
3.1.4 3.0.8 3.0.9 3.1.0 3.1.2 3.1.3 3.0.7 3.0.5 3.0.4 3.0.3 3.0.2 trunk 1.5.0 1.5.1 1.5.2 1.6.1 1.6.2 1.6.3 1.6.4 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4 2.0.5 All 93 releases
← All changes | includes/Admin/Biggopties.php +95 -73 3.0.23.1.4 View file →
@@ -1,8 +1,12 @@
1 1 <?php
2 2
3 3 namespace UltimateStoreKit\Admin;
4 4
5 +if (! defined('ABSPATH')) {
6 + exit; // Exit if accessed directly
7 +}
8 +
5 9 use UltimateStoreKit\Base\Singleton;
6 10
7 11 /**
8 12 * Biggopties class
@@ -9,10 +13,32 @@
9 13 */
10 14 class Biggopties {
11 15 use Singleton;
12 16
17 + /**
18 + * Max seconds to wait for the remote API.
19 + */
20 + const REQUEST_TIMEOUT = 5;
21 +
22 + /**
23 + * How long to skip remote requests after a failure.
24 + */
25 + const FAILURE_BACKOFF = HOUR_IN_SECONDS;
26 +
27 + /**
28 + * How long a successful response stays cached.
29 + */
30 + const CACHE_LIFETIME = HOUR_IN_SECONDS;
31 +
13 32 private static $biggopties = [];
14 33
34 + /**
35 + * Every notice id this plugin renders starts with this. The dismiss handler
36 + * writes the id straight into a transient / user-meta key, so it will only
37 + * accept keys that carry the prefix.
38 + */
39 + const DISMISS_KEY_PREFIX = 'bdt-admin-biggopti-';
40 +
15 41 public function __construct() {
16 42
17 43 // add_action('admin_notices', [$this, 'show_biggopties']);
18 44 add_action('wp_ajax_ultimate-store-kit-biggopties', [$this, 'dismiss']);
@@ -18,73 +44,21 @@
18 44 add_action('wp_ajax_ultimate-store-kit-biggopties', [$this, 'dismiss']);
19 45
20 46 // AJAX endpoint to fetch API biggopties on demand (after page load)
21 47 add_action('wp_ajax_usk_fetch_api_biggopties', [$this, 'ajax_fetch_api_biggopties']);
22 - add_action('wp_ajax_usk_admin_api_biggopti_dismiss', [$this, 'usk_admin_api_biggopti_dismiss']);
23 48 add_action('admin_enqueue_scripts', [$this, 'enqueue_admin_scripts']);
24 49 }
25 50
26 51 /**
27 - * Dismiss Admin API Biggopti.
28 - */
29 - public function usk_admin_api_biggopti_dismiss() {
30 - $nonce = (isset($_POST['_wpnonce'])) ? sanitize_text_field($_POST['_wpnonce']) : '';
31 - $display_id = (isset($_POST['display_id'])) ? sanitize_text_field($_POST['display_id']) : '';
32 - $id = (isset($_POST['id'])) ? esc_attr($_POST['id']) : '';
33 - $meta = (isset($_POST['meta'])) ? esc_attr($_POST['meta']) : '';
34 -
35 - if (! wp_verify_nonce($nonce, 'ultimate-store-kit')) {
36 - wp_send_json_error();
37 - }
38 -
39 - if (! current_user_can('manage_options')) {
40 - wp_send_json_error();
41 - }
42 -
43 - // Prefer display_id; fallback: extract from id (bdt-admin-api-biggopti-{display_id})
44 - if (empty($display_id) && !empty($id)) {
45 - $prefix = 'bdt-admin-api-biggopti-';
46 - if (strpos($id, $prefix) === 0) {
47 - $display_id = substr($id, strlen($prefix));
48 - } else {
49 - $display_id = $id;
50 - }
51 - }
52 -
53 - /**
54 - * Valid inputs?
55 - */
56 - if (!empty($display_id)) {
57 - if ('user' === $meta) {
58 - $user_key = 'bdt-admin-api-biggopti-' . $display_id;
59 - update_user_meta(get_current_user_id(), $user_key, true);
60 - } else {
61 - // Save to options table only - display_id based, no end-time expiration
62 - $dismissals_option = get_option('bdt_biggopti_dismissals', []);
63 - $dismissals_option[$display_id] = ['dismissed_at' => time()];
64 - update_option('bdt_biggopti_dismissals', $dismissals_option, false);
65 - }
66 -
67 - wp_send_json_success();
68 - }
69 -
70 - wp_send_json_error();
71 - }
72 -
73 - /**
74 52 * Enqueue admin scripts
75 53 */
76 54 public function enqueue_admin_scripts() {
77 - wp_enqueue_style('usk-admin-biggopti', BDTUSK_ASSETS_URL . 'admin/others/css/admin-biggopti.css', [], BDTUSK_VER);
78 - wp_enqueue_style('bdt-admin-api-biggopti', BDTUSK_ASSETS_URL . 'admin/others/css/admin-api-biggopti.css', [], BDTUSK_VER);
79 - wp_enqueue_style('bdt-product-feed', BDTUSK_ASSETS_URL . 'admin/others/css/product-feed.css', [], BDTUSK_VER);
55 + wp_enqueue_style('ultimate-store-kit-product-feed', BDTUSK_ASSETS_URL . 'admin/others/css/product-feed.css', [], BDTUSK_VER);
80 56 wp_enqueue_script('usk-biggopti', BDTUSK_ASSETS_URL . 'admin/others/js/biggopti.js', ['jquery'], BDTUSK_VER, true);
81 - wp_enqueue_script('usk-admin-api-biggopti', BDTUSK_ASSETS_URL . 'admin/others/js/admin-api-biggopti.js', ['jquery', 'wp-i18n'], BDTUSK_VER, true);
82 - wp_set_script_translations('usk-admin-api-biggopti', 'ultimate-store-kit');
83 57
84 58 $dismissals = get_option('bdt_biggopti_dismissals', []);
85 59 $dismissed_display_ids = [];
86 - $prefix = 'bdt-admin-biggopti-api-biggopti-';
60 + $prefix = self::DISMISS_KEY_PREFIX . 'api-biggopti-';
87 61 foreach (array_keys($dismissals) as $key) {
88 62 if (strpos($key, $prefix) === 0) {
89 63 $dismissed_display_ids[] = substr($key, strlen($prefix));
90 64 } else {
@@ -92,8 +66,9 @@
92 66 }
93 67 }
94 68
95 69 $current_sector = '';
70 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only check of which admin screen is showing.
96 71 if (isset($_GET['page']) && $_GET['page'] === 'ultimate_store_kit_options') {
97 72 $current_sector = 'plugin_dashboard';
98 73 }
99 74
@@ -99,9 +74,9 @@
99 74
100 75 $script_config = [
101 76 'ajaxurl' => admin_url('admin-ajax.php'),
102 77 'nonce' => wp_create_nonce('ultimate-store-kit'),
103 - 'isPro' => function_exists('usk_license_validation') && usk_license_validation(),
78 + 'isPro' => function_exists('ultimate_store_kit_license_validation') && ultimate_store_kit_license_validation(),
104 79 'assetsUrl' => defined('BDTUSK_ASSETS_URL') ? BDTUSK_ASSETS_URL : '',
105 80 'dismissedDisplayIds' => $dismissed_display_ids,
106 81 'currentSector' => $current_sector,
107 82 ];
@@ -106,9 +81,8 @@
106 81 'currentSector' => $current_sector,
107 82 ];
108 83
109 84 wp_localize_script('usk-biggopti', 'UltimateStoreKitBiggoptiConfig', $script_config);
110 - wp_localize_script('usk-admin-api-biggopti', 'UltimateStoreKitAdminApiBiggoptiConfig', $script_config);
111 85 }
112 86
113 87 /**
114 88 * Get Remote Biggopties Data from API
@@ -116,27 +90,59 @@
116 90 * @return array|mixed
117 91 */
118 92 private function get_api_biggopties_data() {
119 93 // API endpoint for biggopties - you can change this to your actual endpoint
120 - $api_url = 'https://api.sigmative.io/prod/store/api/biggopti/api-data-records';
94 + $api_url = '';
95 + $transient_key = 'ultimate_store_kit_biggopties_api';
121 96
97 + $cached_data = get_transient($transient_key);
98 +
99 + if (! empty($cached_data)) {
100 + $biggopties = json_decode($cached_data);
101 + return $this->extract_biggopties($biggopties);
102 + }
103 +
104 + /**
105 + * A recent request failed, so don't retry on every admin page load.
106 + */
107 + if (get_transient($transient_key . '_failed')) {
108 + return [];
109 + }
110 +
122 111 $response = wp_remote_get($api_url, [
123 - 'timeout' => 30,
112 + 'timeout' => self::REQUEST_TIMEOUT,
124 113 'headers' => [
125 114 'Accept' => 'application/json',
126 115 ],
127 116 ]);
128 117
129 - if (is_wp_error($response)) {
118 + if (is_wp_error($response) || 200 !== (int) wp_remote_retrieve_response_code($response)) {
119 + set_transient($transient_key . '_failed', 1, self::FAILURE_BACKOFF);
130 120 return [];
131 121 }
132 122
133 - $response_code = wp_remote_retrieve_response_code($response);
134 -
135 123 $response_body = wp_remote_retrieve_body($response);
136 124
137 125 $biggopties = json_decode($response_body);
138 126
127 + if (null === $biggopties) {
128 + set_transient($transient_key . '_failed', 1, self::FAILURE_BACKOFF);
129 + return [];
130 + }
131 +
132 + set_transient($transient_key, $response_body, self::CACHE_LIFETIME);
133 +
134 + return $this->extract_biggopties($biggopties);
135 + }
136 +
137 + /**
138 + * Pull this plugin's records out of the decoded API payload.
139 + *
140 + * @param mixed $biggopties Decoded API response.
141 + * @return array
142 + */
143 + private function extract_biggopties($biggopties) {
144 +
139 145 if (isset($biggopties) && isset($biggopties->{'ultimate-store-kit'})) {
140 146 $data = $biggopties->{'ultimate-store-kit'};
141 147 if (is_array($data)) {
142 148 return $data;
@@ -212,9 +218,9 @@
212 218 */
213 219 private function is_biggopti_compatible_with_plugin($biggopti) {
214 220 // Get current plugin info
215 221 $current_plugin_slug = $this->get_current_plugin_slug();
216 - $is_pro_active = function_exists('_is_usk_pro_activated') ? _is_usk_pro_activated() : false;
222 + $is_pro_active = function_exists('ultimate_store_kit_is_pro_activated') ? ultimate_store_kit_is_pro_activated() : false;
217 223 $is_lite_active = $current_plugin_slug === 'ultimate-store-kit';
218 224 $is_pro_plugin = $current_plugin_slug === 'ultimate-store-kit-pro';
219 225
220 226 // Get client targets, default to ['both'] if not set or not an array
@@ -293,18 +299,18 @@
293 299 $background_style = '';
294 300 $wrapper_classes = 'bdt-biggopti-wrapper';
295 301
296 302 if (isset($biggopti->background_color) && !empty($biggopti->background_color)) {
297 - $background_style .= 'background-color: ' . esc_attr($biggopti->background_color) . ';';
303 + $background_style .= 'background-color: ' . sanitize_text_field($biggopti->background_color) . ';';
298 304 }
299 305
300 306 if (isset($biggopti->image) && !empty($biggopti->image)) {
301 - $background_style .= 'background-image: url(' . esc_url($biggopti->image) . ');';
307 + $background_style .= 'background-image: url(' . esc_url_raw($biggopti->image) . ');';
302 308 $wrapper_classes .= ' has-background-image';
303 309 }
304 310
305 311 ?>
306 - <div class="<?php echo esc_attr($wrapper_classes); ?>" <?php echo $background_style ? 'style="' . $background_style . '"' : ''; ?>>
312 + <div class="<?php echo esc_attr($wrapper_classes); ?>" <?php echo $background_style ? 'style="' . esc_attr($background_style) . '"' : ''; ?>>
307 313
308 314
309 315 <?php $title = (isset($biggopti->title) && !empty($biggopti->title)) ? $biggopti->title : ''; ?>
310 316
@@ -376,9 +382,9 @@
376 382 /**
377 383 * AJAX: Build and return API biggopties HTML for dynamic injection
378 384 */
379 385 public function ajax_fetch_api_biggopties() {
380 - $nonce = isset($_POST['_wpnonce']) ? sanitize_text_field($_POST['_wpnonce']) : '';
386 + $nonce = isset($_POST['_wpnonce']) ? sanitize_text_field(wp_unslash($_POST['_wpnonce'])) : '';
381 387 if (!wp_verify_nonce($nonce, 'ultimate-store-kit')) {
382 388 wp_send_json_error(['message' => 'invalid_nonce']);
383 389 }
384 390
@@ -386,9 +392,9 @@
386 392 wp_send_json_error(['message' => 'forbidden']);
387 393 }
388 394
389 395 // Don't show biggopties on plugin/theme install and upload pages
390 - $current_url = isset($_POST['current_url']) ? sanitize_text_field($_POST['current_url']) : '';
396 + $current_url = isset($_POST['current_url']) ? sanitize_text_field(wp_unslash($_POST['current_url'])) : '';
391 397
392 398 if (!empty($current_url)) {
393 399 $excluded_patterns = [
394 400 'plugin-install.php',
@@ -443,12 +449,15 @@
443 449 /**
444 450 * Dismiss Biggopti.
445 451 */
446 452 public function dismiss() {
447 - $nonce = (isset($_POST['_wpnonce'])) ? sanitize_text_field($_POST['_wpnonce']) : '';
448 - $id = (isset($_POST['id'])) ? esc_attr($_POST['id']) : '';
449 - $time = (isset($_POST['time'])) ? esc_attr($_POST['time']) : '';
450 - $meta = (isset($_POST['meta'])) ? esc_attr($_POST['meta']) : '';
453 + $nonce = (isset($_POST['_wpnonce'])) ? sanitize_text_field(wp_unslash($_POST['_wpnonce'])) : '';
454 + // Not sanitize_key(): the id is echoed back from the notice markup and has to
455 + // match the key show_biggopties() reads verbatim, so it is validated below
456 + // rather than rewritten here.
457 + $id = (isset($_POST['id'])) ? sanitize_text_field(wp_unslash($_POST['id'])) : '';
458 + $time = (isset($_POST['time'])) ? absint(wp_unslash($_POST['time'])) : 0;
459 + $meta = (isset($_POST['meta'])) ? sanitize_key(wp_unslash($_POST['meta'])) : '';
451 460
452 461 if (! wp_verify_nonce($nonce, 'ultimate-store-kit')) {
453 462 wp_send_json_error();
454 463 }
@@ -457,8 +466,21 @@
457 466 wp_send_json_error();
458 467 }
459 468
460 469 /**
470 + * The id becomes a transient or user-meta key, so it has to be one of ours.
471 + * Every notice rendered by show_biggopties() carries this prefix; anything
472 + * else is a request to write a key this handler has no business writing.
473 + */
474 + if (! preg_match('/^' . preg_quote(self::DISMISS_KEY_PREFIX, '/') . '[A-Za-z0-9_.-]{1,120}$/', $id)) {
475 + wp_send_json_error();
476 + }
477 +
478 + // Likewise the lifetime is request-supplied — keep it inside a sane window
479 + // so a dismissal cannot be made effectively permanent.
480 + $time = min(max($time, MINUTE_IN_SECONDS), YEAR_IN_SECONDS);
481 +
482 + /**
461 483 * Valid inputs?
462 484 */
463 485 if (!empty($id)) {
464 486 // Handle regular biggopties
@@ -526,12 +548,12 @@
526 548 $biggopti['data'] = ' dismissible-time=' . esc_attr($biggopti['dismissible-time']) . ' ';
527 549 }
528 550
529 551 // Biggopti ID.
530 - $biggopti_id = 'bdt-admin-biggopti-' . $biggopti['id'];
552 + $biggopti_id = self::DISMISS_KEY_PREFIX . $biggopti['id'];
531 553 $biggopti['id'] = $biggopti_id;
532 554 if (!isset($biggopti['id'])) {
533 - $biggopti_id = 'bdt-admin-biggopti-' . $biggopti['id'];
555 + $biggopti_id = self::DISMISS_KEY_PREFIX . $biggopti['id'];
534 556 $biggopti['id'] = $biggopti_id;
535 557 } else {
536 558 $biggopti_id = $biggopti['id'];
537 559 }