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/builder/builder-integration.php +72 -88 3.0.33.1.4 View file →
@@ -1,8 +1,14 @@
1 1 <?php
2 2
3 3 namespace UltimateStoreKit\Builder;
4 4
5 +if (! defined('ABSPATH')) {
6 + exit; // Exit if accessed directly
7 +}
8 +
9 +// phpcs:disable WordPress.NamingConventions.PrefixAllGlobals -- BDTUSK_ / ultimate_store_kit_ / ultimate-store-kit- are this plugin's established public prefixes.
10 +
5 11 if (! defined('WPINC')) {
6 12 die;
7 13 }
8 14
@@ -29,38 +35,16 @@
29 35 add_filter("elementor/document/urls/wp_preview", [$this, 'change_preview_editor_url'], 999, 2);
30 36
31 37 add_action('elementor/documents/register_controls', [$this, 'register_document_controls']);
32 38
33 - // Add demo bypass filter for template preview
34 - add_filter('ultimate_store_kit/preview/verified_bypass', function ($verify) {
35 - // Check if we're in a demo environment or development site
36 - // For demo sites, you might want to check domain names or other indicators
37 - $demo_hosts = apply_filters('ultimate_store_kit/demo_hosts', [
38 - 'storekit.pro',
39 - 'demo.storekit.pro',
40 - 'localhost',
41 - '127.0.0.1'
42 - ]);
43 -
44 - $current_host = isset($_SERVER['HTTP_HOST']) ? sanitize_text_field($_SERVER['HTTP_HOST']) : '';
45 -
46 - foreach ($demo_hosts as $host) {
47 - if (strpos($current_host, $host) !== false) {
48 - return true;
49 - }
50 - }
51 -
52 - return $verify;
53 - });
54 -
55 - // Add filter to enable demo mode for preview URLs
56 - add_filter('ultimate_store_kit/preview/use_demo_bypass', function ($use_demo) {
57 - // Enable demo bypass in Elementor editor
58 - if (isset($_GET['action']) && $_GET['action'] === 'elementor') {
59 - return true;
60 - }
61 - return $use_demo;
62 - });
39 + // The template preview used to accept the literal string "verified" in place
40 + // of a nonce whenever $_SERVER['HTTP_HOST'] contained one of a list of demo
41 + // hostnames. Because Host is attacker-controlled and the check was a substring
42 + // match, any unauthenticated visitor could render the Elementor content of an
43 + // arbitrary post id -- including drafts and private posts -- on any site whose
44 + // Host reached PHP as localhost/127.0.0.1 (common behind a reverse proxy) or
45 + // whose domain merely contained one of those strings. Preview is now gated on
46 + // the real per-post nonce plus an edit_post capability check; see get_template_id().
63 47 }
64 48
65 49 public function change_preview_editor_url($url, $document) {
66 50 $post_id = $document->get_main_id();
@@ -104,15 +88,11 @@
104 88
105 89 if (!empty($product_tags) && !is_wp_error($product_tags)) {
106 90 $template_url = get_term_link($product_tags[0]);
107 91 }
108 - }
109 -
110 - elseif ($template_slug === 'shop' || $template_slug === 'archive') {
92 + } elseif ($template_slug === 'shop' || $template_slug === 'archive') {
111 93 $template_url = get_permalink(wc_get_page_id('shop'));
112 - }
113 -
114 - elseif ($template_slug === 'single' && $post_type === 'product') {
94 + } elseif ($template_slug === 'single' && $post_type === 'product') {
115 95 $page_settings_manager = \Elementor\Core\Settings\Manager::get_settings_managers('page');
116 96 $page_settings_model = $page_settings_manager->get_model($post_id);
117 97 $sample_product = $page_settings_model->get_settings('usk_builder_sample_post_id');
118 98
@@ -118,25 +98,19 @@
118 98
119 99 $template_url = !empty($sample_product) ?
120 100 get_permalink($sample_product) :
121 101 $this->get_default_product_url();
122 - }
123 -
124 - elseif ($template_slug === 'cart') {
102 + } elseif ($template_slug === 'cart') {
125 103 $template_url = wc_get_cart_url();
126 - }
127 -
128 - elseif ($template_slug === 'checkout') {
104 + } elseif ($template_slug === 'checkout') {
129 105 $template_url = wc_get_checkout_url();
130 - }
131 -
132 - elseif ($template_slug === 'myaccount'
106 + } elseif (
107 + $template_slug === 'myaccount'
133 108 || $template_slug === 'login'
134 - || strpos($template_slug, 'myaccount-') === 0) {
109 + || strpos($template_slug, 'myaccount-') === 0
110 + ) {
135 111 $template_url = get_permalink(wc_get_page_id('myaccount'));
136 - }
137 -
138 - elseif ($template_slug === 'order-received') {
112 + } elseif ($template_slug === 'order-received') {
139 113 $orders = wc_get_orders(['limit' => 1]);
140 114 if (!empty($orders)) {
141 115 $order = $orders[0];
142 116 $order_id = $order->get_id();
@@ -147,15 +121,12 @@
147 121 if (empty($template_url)) {
148 122 return $url;
149 123 }
150 124
151 - $is_demo = apply_filters('ultimate_store_kit/preview/use_demo_bypass', false);
152 - $nonce_value = $is_demo ? 'verified' : wp_create_nonce('template_preview_' . $post_id);
153 -
154 125 $param = [
155 - 'usk_template_id' => $post_id,
156 - 'preview_nonce' => $nonce_value,
157 - 'preview' => true
126 + 'ultimate_store_kit_template_id' => $post_id,
127 + 'ultimate_store_kit_preview_nonce' => wp_create_nonce('ultimate_store_kit_template_preview_' . $post_id),
128 + 'preview' => true
158 129 ];
159 130
160 131 // Add parameters two URL
161 132 $url = add_query_arg($param, $template_url);
@@ -164,8 +135,9 @@
164 135 }
165 136
166 137 public function my_custom_fonts() {
167 138 if (is_admin() && Plugin::instance()->editor->is_edit_mode()) {
139 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only check on whether a builder template is open; only enqueues a style.
168 140 if (isset($_REQUEST['usk-template'])) {
169 141 wp_register_style('usk-template-builder-hide-preview-btn-inline', false); // phpcs:ignore
170 142 wp_enqueue_style('usk-template-builder-hide-preview-btn-inline');
171 143 wp_add_inline_style(
@@ -203,9 +175,9 @@
203 175 return;
204 176 }
205 177 $meta = get_post_meta($post->ID);
206 178
207 - $templateMeta = optional($meta)[Meta::TEMPLATE_TYPE];
179 + $templateMeta = ultimate_store_kit_optional($meta)[Meta::TEMPLATE_TYPE];
208 180 if (! isset($templateMeta[0])) {
209 181 return;
210 182 }
211 183 $postMeta = $templateMeta[0];
@@ -285,29 +257,30 @@
285 257 return $template;
286 258 }
287 259 }
288 260
289 -
261 +
290 262 if (is_post_type_archive('product') || is_page(wc_get_page_id('shop')) || is_product_taxonomy()) {
291 263 $template_type = 'shop';
292 -
264 +
293 265 if (is_tax('product_cat')) {
294 266 $template_type = 'category';
295 267 } elseif (is_tax('product_tag')) {
296 268 $template_type = 'tag';
297 269 }
298 -
270 +
299 271 if ($custom_template = $this->get_template_id($template_type)) {
300 272 $this->current_template_id = $custom_template;
301 273 return $this->getTemplatePath('woocommerce/archive-product', $template);
302 274 }
303 275 }
304 -
305 276
306 - if (is_cart()) {
307 - if ($custom_template = $this->get_template_id('cart', 'product')) {
308 - $this->current_template_id = $custom_template;
309 - return $this->getTemplatePath('woocommerce/cart', $template);
277 +
278 + if ( is_cart() ) {
279 + $cart_template = $this->resolve_cart_template( $template );
280 +
281 + if ( $cart_template ) {
282 + return $cart_template;
310 283 }
311 284 }
312 285
313 286 if (is_order_received_page()) {
@@ -346,13 +319,13 @@
346 319 // single Login & Register template, which is responsible for both
347 320 // authentication flows. WooCommerce's own form-login.php renders
348 321 // the login and registration forms on the same URL, so one
349 322 // template covers both intents.
350 - if ( ! is_user_logged_in() && empty($endpoint_match) ) {
351 - if ( $custom_template = $this->get_template_id('login', 'account') ) {
323 + if (! is_user_logged_in() && empty($endpoint_match)) {
324 + if ($custom_template = $this->get_template_id('login', 'account')) {
352 325 $this->current_template_id = $custom_template;
353 326
354 - if ( $newTemplate = $this->getTemplatePath('woocommerce/login') ) {
327 + if ($newTemplate = $this->getTemplatePath('woocommerce/login')) {
355 328 return $newTemplate;
356 329 }
357 330
358 331 return $this->getTemplatePath('woocommerce/my-account', $template);
@@ -405,9 +378,9 @@
405 378 return $this->getTemplatePath('home', $template);
406 379 }
407 380 }
408 381
409 - if ($page_Id = intval(get_option('bdt_usk_compare_products_page_id'))) {
382 + if ($page_Id = ultimate_store_kit_get_compare_page_option()) {
410 383 if (is_page($page_Id)) {
411 384 if ($custom_template = $this->get_template_id('compare-products', 'product')) {
412 385 $this->current_template_id = $custom_template;
413 386 return $this->getTemplatePath('home', $template);
@@ -449,32 +422,26 @@
449 422 if (null !== $this->current_template_id) {
450 423 return $this->current_template_id;
451 424 }
452 425
453 - // Handle template preview from URL parameters
454 - if (!empty($_GET['preview']) && !empty($_GET['usk_template_id']) && !empty($_GET['preview_nonce'])) {
455 - $usk_template_id = sanitize_text_field(wp_unslash($_GET['usk_template_id']));
456 - $nonce = sanitize_text_field(wp_unslash($_GET['preview_nonce']));
426 + // Handle template preview from URL parameters.
427 + if (!empty($_GET['preview']) && !empty($_GET['ultimate_store_kit_template_id']) && !empty($_GET['ultimate_store_kit_preview_nonce'])) {
428 + $usk_template_id = absint(wp_unslash($_GET['ultimate_store_kit_template_id']));
429 + $nonce = sanitize_text_field(wp_unslash($_GET['ultimate_store_kit_preview_nonce']));
457 430
458 - // Special handling for demo bypass
459 - $is_demo_bypass = ($nonce === 'verified');
460 - $nonce_verified = $is_demo_bypass ?
461 - apply_filters('ultimate_store_kit/preview/verified_bypass', false) :
462 - wp_verify_nonce($nonce, 'template_preview_' . $usk_template_id);
431 + // The nonce is bound to the specific template and to the user who
432 + // generated it, and the capability check makes sure a leaked preview
433 + // URL cannot be replayed by someone who may not edit the template.
434 + $nonce_verified = $usk_template_id
435 + && wp_verify_nonce($nonce, 'ultimate_store_kit_template_preview_' . $usk_template_id)
436 + && current_user_can('edit_post', $usk_template_id);
463 437
464 - if ($nonce_verified) {
465 - // For demo bypass mode, we don't need to check template type
466 - if ($is_demo_bypass) {
467 - $this->current_template_id = (int)$usk_template_id;
468 - return $this->current_template_id;
469 - }
470 -
471 - // For normal preview, check template type
438 + if ($nonce_verified && get_post_type($usk_template_id) === Meta::POST_TYPE) {
472 439 $template_type = get_post_meta($usk_template_id, Meta::TEMPLATE_TYPE, true);
473 440 if (!empty($template_type)) {
474 441 $template_data = explode(Builder_Template_Helper::separator(), $template_type);
475 442 if (count($template_data) >= 2 && $template_data[1] === $slug && ($postType === false || $template_data[0] === $postType)) {
476 - $this->current_template_id = (int)$usk_template_id;
443 + $this->current_template_id = $usk_template_id;
477 444 return $this->current_template_id;
478 445 }
479 446 }
480 447 }
@@ -510,8 +477,25 @@
510 477 return $default;
511 478 }
512 479
513 480 /**
481 + * Resolve the plugin cart template for WooCommerce cart requests.
482 + *
483 + * @param string $template Default WordPress template path.
484 + * @return string|false
485 + */
486 + protected function resolve_cart_template( $template ) {
487 + if ( ! Cart_Render::is_available() ) {
488 + return false;
489 + }
490 +
491 + $custom_template = $this->get_template_id( 'cart', 'product' );
492 + $this->current_template_id = $custom_template ? absint( $custom_template ) : null;
493 +
494 + return $this->getTemplatePath( 'woocommerce/cart', $template );
495 + }
496 +
497 + /**
514 498 * Get default product URL with proper error checking
515 499 *
516 500 * @return string
517 501 */
@@ -516,9 +500,9 @@
516 500 * @return string
517 501 */
518 502 protected function get_default_product_url() {
519 503 $products = wc_get_products(['status' => 'publish', 'limit' => 1]);
520 -
504 +
521 505 if (!empty($products) && isset($products[0]) && is_object($products[0])) {
522 506 $product = $products[0];
523 507 if (method_exists($product, 'get_id')) {
524 508 return get_permalink($product->get_id());
@@ -523,9 +507,9 @@
523 507 if (method_exists($product, 'get_id')) {
524 508 return get_permalink($product->get_id());
525 509 }
526 510 }
527 -
511 +
528 512 // Fallback to shop page if no products found
529 513 return get_permalink(wc_get_page_id('shop'));
530 514 }
531 515 }