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 -72 3.0.43.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,72 +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('bdt-admin-api-biggopti', BDTUSK_ASSETS_URL . 'admin/others/css/admin-api-biggopti.css', [], BDTUSK_VER);
78 - 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);
79 56 wp_enqueue_script('usk-biggopti', BDTUSK_ASSETS_URL . 'admin/others/js/biggopti.js', ['jquery'], BDTUSK_VER, true);
80 - wp_enqueue_script('usk-admin-api-biggopti', BDTUSK_ASSETS_URL . 'admin/others/js/admin-api-biggopti.js', ['jquery', 'wp-i18n'], BDTUSK_VER, true);
81 - wp_set_script_translations('usk-admin-api-biggopti', 'ultimate-store-kit');
82 57
83 58 $dismissals = get_option('bdt_biggopti_dismissals', []);
84 59 $dismissed_display_ids = [];
85 - $prefix = 'bdt-admin-biggopti-api-biggopti-';
60 + $prefix = self::DISMISS_KEY_PREFIX . 'api-biggopti-';
86 61 foreach (array_keys($dismissals) as $key) {
87 62 if (strpos($key, $prefix) === 0) {
88 63 $dismissed_display_ids[] = substr($key, strlen($prefix));
89 64 } else {
@@ -91,8 +66,9 @@
91 66 }
92 67 }
93 68
94 69 $current_sector = '';
70 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only check of which admin screen is showing.
95 71 if (isset($_GET['page']) && $_GET['page'] === 'ultimate_store_kit_options') {
96 72 $current_sector = 'plugin_dashboard';
97 73 }
98 74
@@ -98,9 +74,9 @@
98 74
99 75 $script_config = [
100 76 'ajaxurl' => admin_url('admin-ajax.php'),
101 77 'nonce' => wp_create_nonce('ultimate-store-kit'),
102 - 'isPro' => function_exists('usk_license_validation') && usk_license_validation(),
78 + 'isPro' => function_exists('ultimate_store_kit_license_validation') && ultimate_store_kit_license_validation(),
103 79 'assetsUrl' => defined('BDTUSK_ASSETS_URL') ? BDTUSK_ASSETS_URL : '',
104 80 'dismissedDisplayIds' => $dismissed_display_ids,
105 81 'currentSector' => $current_sector,
106 82 ];
@@ -105,9 +81,8 @@
105 81 'currentSector' => $current_sector,
106 82 ];
107 83
108 84 wp_localize_script('usk-biggopti', 'UltimateStoreKitBiggoptiConfig', $script_config);
109 - wp_localize_script('usk-admin-api-biggopti', 'UltimateStoreKitAdminApiBiggoptiConfig', $script_config);
110 85 }
111 86
112 87 /**
113 88 * Get Remote Biggopties Data from API
@@ -115,27 +90,59 @@
115 90 * @return array|mixed
116 91 */
117 92 private function get_api_biggopties_data() {
118 93 // API endpoint for biggopties - you can change this to your actual endpoint
119 - $api_url = 'https://api.sigmative.io/prod/store/api/biggopti/api-data-records';
94 + $api_url = '';
95 + $transient_key = 'ultimate_store_kit_biggopties_api';
120 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 +
121 111 $response = wp_remote_get($api_url, [
122 - 'timeout' => 30,
112 + 'timeout' => self::REQUEST_TIMEOUT,
123 113 'headers' => [
124 114 'Accept' => 'application/json',
125 115 ],
126 116 ]);
127 117
128 - 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);
129 120 return [];
130 121 }
131 122
132 - $response_code = wp_remote_retrieve_response_code($response);
133 -
134 123 $response_body = wp_remote_retrieve_body($response);
135 124
136 125 $biggopties = json_decode($response_body);
137 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 +
138 145 if (isset($biggopties) && isset($biggopties->{'ultimate-store-kit'})) {
139 146 $data = $biggopties->{'ultimate-store-kit'};
140 147 if (is_array($data)) {
141 148 return $data;
@@ -211,9 +218,9 @@
211 218 */
212 219 private function is_biggopti_compatible_with_plugin($biggopti) {
213 220 // Get current plugin info
214 221 $current_plugin_slug = $this->get_current_plugin_slug();
215 - $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;
216 223 $is_lite_active = $current_plugin_slug === 'ultimate-store-kit';
217 224 $is_pro_plugin = $current_plugin_slug === 'ultimate-store-kit-pro';
218 225
219 226 // Get client targets, default to ['both'] if not set or not an array
@@ -292,18 +299,18 @@
292 299 $background_style = '';
293 300 $wrapper_classes = 'bdt-biggopti-wrapper';
294 301
295 302 if (isset($biggopti->background_color) && !empty($biggopti->background_color)) {
296 - $background_style .= 'background-color: ' . esc_attr($biggopti->background_color) . ';';
303 + $background_style .= 'background-color: ' . sanitize_text_field($biggopti->background_color) . ';';
297 304 }
298 305
299 306 if (isset($biggopti->image) && !empty($biggopti->image)) {
300 - $background_style .= 'background-image: url(' . esc_url($biggopti->image) . ');';
307 + $background_style .= 'background-image: url(' . esc_url_raw($biggopti->image) . ');';
301 308 $wrapper_classes .= ' has-background-image';
302 309 }
303 310
304 311 ?>
305 - <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) . '"' : ''; ?>>
306 313
307 314
308 315 <?php $title = (isset($biggopti->title) && !empty($biggopti->title)) ? $biggopti->title : ''; ?>
309 316
@@ -375,9 +382,9 @@
375 382 /**
376 383 * AJAX: Build and return API biggopties HTML for dynamic injection
377 384 */
378 385 public function ajax_fetch_api_biggopties() {
379 - $nonce = isset($_POST['_wpnonce']) ? sanitize_text_field($_POST['_wpnonce']) : '';
386 + $nonce = isset($_POST['_wpnonce']) ? sanitize_text_field(wp_unslash($_POST['_wpnonce'])) : '';
380 387 if (!wp_verify_nonce($nonce, 'ultimate-store-kit')) {
381 388 wp_send_json_error(['message' => 'invalid_nonce']);
382 389 }
383 390
@@ -385,9 +392,9 @@
385 392 wp_send_json_error(['message' => 'forbidden']);
386 393 }
387 394
388 395 // Don't show biggopties on plugin/theme install and upload pages
389 - $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'])) : '';
390 397
391 398 if (!empty($current_url)) {
392 399 $excluded_patterns = [
393 400 'plugin-install.php',
@@ -442,12 +449,15 @@
442 449 /**
443 450 * Dismiss Biggopti.
444 451 */
445 452 public function dismiss() {
446 - $nonce = (isset($_POST['_wpnonce'])) ? sanitize_text_field($_POST['_wpnonce']) : '';
447 - $id = (isset($_POST['id'])) ? esc_attr($_POST['id']) : '';
448 - $time = (isset($_POST['time'])) ? esc_attr($_POST['time']) : '';
449 - $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'])) : '';
450 460
451 461 if (! wp_verify_nonce($nonce, 'ultimate-store-kit')) {
452 462 wp_send_json_error();
453 463 }
@@ -456,8 +466,21 @@
456 466 wp_send_json_error();
457 467 }
458 468
459 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 + /**
460 483 * Valid inputs?
461 484 */
462 485 if (!empty($id)) {
463 486 // Handle regular biggopties
@@ -525,12 +548,12 @@
525 548 $biggopti['data'] = ' dismissible-time=' . esc_attr($biggopti['dismissible-time']) . ' ';
526 549 }
527 550
528 551 // Biggopti ID.
529 - $biggopti_id = 'bdt-admin-biggopti-' . $biggopti['id'];
552 + $biggopti_id = self::DISMISS_KEY_PREFIX . $biggopti['id'];
530 553 $biggopti['id'] = $biggopti_id;
531 554 if (!isset($biggopti['id'])) {
532 - $biggopti_id = 'bdt-admin-biggopti-' . $biggopti['id'];
555 + $biggopti_id = self::DISMISS_KEY_PREFIX . $biggopti['id'];
533 556 $biggopti['id'] = $biggopti_id;
534 557 } else {
535 558 $biggopti_id = $biggopti['id'];
536 559 }