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 +90 -85 3.0.23.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,23 +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' || strpos($template_slug, 'myaccount-') === 0) {
106 + } elseif (
107 + $template_slug === 'myaccount'
108 + || $template_slug === 'login'
109 + || strpos($template_slug, 'myaccount-') === 0
110 + ) {
133 111 $template_url = get_permalink(wc_get_page_id('myaccount'));
134 - }
135 -
136 - elseif ($template_slug === 'order-received') {
112 + } elseif ($template_slug === 'order-received') {
137 113 $orders = wc_get_orders(['limit' => 1]);
138 114 if (!empty($orders)) {
139 115 $order = $orders[0];
140 116 $order_id = $order->get_id();
@@ -145,15 +121,12 @@
145 121 if (empty($template_url)) {
146 122 return $url;
147 123 }
148 124
149 - $is_demo = apply_filters('ultimate_store_kit/preview/use_demo_bypass', false);
150 - $nonce_value = $is_demo ? 'verified' : wp_create_nonce('template_preview_' . $post_id);
151 -
152 125 $param = [
153 - 'usk_template_id' => $post_id,
154 - 'preview_nonce' => $nonce_value,
155 - '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
156 129 ];
157 130
158 131 // Add parameters two URL
159 132 $url = add_query_arg($param, $template_url);
@@ -162,8 +135,9 @@
162 135 }
163 136
164 137 public function my_custom_fonts() {
165 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.
166 140 if (isset($_REQUEST['usk-template'])) {
167 141 wp_register_style('usk-template-builder-hide-preview-btn-inline', false); // phpcs:ignore
168 142 wp_enqueue_style('usk-template-builder-hide-preview-btn-inline');
169 143 wp_add_inline_style(
@@ -201,9 +175,9 @@
201 175 return;
202 176 }
203 177 $meta = get_post_meta($post->ID);
204 178
205 - $templateMeta = optional($meta)[Meta::TEMPLATE_TYPE];
179 + $templateMeta = ultimate_store_kit_optional($meta)[Meta::TEMPLATE_TYPE];
206 180 if (! isset($templateMeta[0])) {
207 181 return;
208 182 }
209 183 $postMeta = $templateMeta[0];
@@ -283,29 +257,30 @@
283 257 return $template;
284 258 }
285 259 }
286 260
287 -
261 +
288 262 if (is_post_type_archive('product') || is_page(wc_get_page_id('shop')) || is_product_taxonomy()) {
289 263 $template_type = 'shop';
290 -
264 +
291 265 if (is_tax('product_cat')) {
292 266 $template_type = 'category';
293 267 } elseif (is_tax('product_tag')) {
294 268 $template_type = 'tag';
295 269 }
296 -
270 +
297 271 if ($custom_template = $this->get_template_id($template_type)) {
298 272 $this->current_template_id = $custom_template;
299 273 return $this->getTemplatePath('woocommerce/archive-product', $template);
300 274 }
301 275 }
302 -
303 276
304 - if (is_cart()) {
305 - if ($custom_template = $this->get_template_id('cart', 'product')) {
306 - $this->current_template_id = $custom_template;
307 - 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;
308 283 }
309 284 }
310 285
311 286 if (is_order_received_page()) {
@@ -337,9 +312,28 @@
337 312 * Add wishlist endpoint
338 313 */
339 314 $query_vars['wishlist'] = 'wishlist';
340 315
341 - if ($endpoint = array_intersect_key($wp->query_vars, $query_vars)) {
316 + $endpoint_match = array_intersect_key($wp->query_vars, $query_vars);
317 +
318 + // Logged-out visitors landing on /my-account/ (no endpoint) see the
319 + // single Login & Register template, which is responsible for both
320 + // authentication flows. WooCommerce's own form-login.php renders
321 + // the login and registration forms on the same URL, so one
322 + // template covers both intents.
323 + if (! is_user_logged_in() && empty($endpoint_match)) {
324 + if ($custom_template = $this->get_template_id('login', 'account')) {
325 + $this->current_template_id = $custom_template;
326 +
327 + if ($newTemplate = $this->getTemplatePath('woocommerce/login')) {
328 + return $newTemplate;
329 + }
330 +
331 + return $this->getTemplatePath('woocommerce/my-account', $template);
332 + }
333 + }
334 +
335 + if ($endpoint = $endpoint_match) {
342 336 $endpoint = array_key_first($endpoint);
343 337
344 338 if ($endpoint && $custom_template = $this->get_template_id($endpoint, 'account')) {
345 339 $this->current_template_id = $custom_template;
@@ -384,9 +378,9 @@
384 378 return $this->getTemplatePath('home', $template);
385 379 }
386 380 }
387 381
388 - if ($page_Id = intval(get_option('bdt_usk_compare_products_page_id'))) {
382 + if ($page_Id = ultimate_store_kit_get_compare_page_option()) {
389 383 if (is_page($page_Id)) {
390 384 if ($custom_template = $this->get_template_id('compare-products', 'product')) {
391 385 $this->current_template_id = $custom_template;
392 386 return $this->getTemplatePath('home', $template);
@@ -428,32 +422,26 @@
428 422 if (null !== $this->current_template_id) {
429 423 return $this->current_template_id;
430 424 }
431 425
432 - // Handle template preview from URL parameters
433 - if (!empty($_GET['preview']) && !empty($_GET['usk_template_id']) && !empty($_GET['preview_nonce'])) {
434 - $usk_template_id = sanitize_text_field(wp_unslash($_GET['usk_template_id']));
435 - $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']));
436 430
437 - // Special handling for demo bypass
438 - $is_demo_bypass = ($nonce === 'verified');
439 - $nonce_verified = $is_demo_bypass ?
440 - apply_filters('ultimate_store_kit/preview/verified_bypass', false) :
441 - 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);
442 437
443 - if ($nonce_verified) {
444 - // For demo bypass mode, we don't need to check template type
445 - if ($is_demo_bypass) {
446 - $this->current_template_id = (int)$usk_template_id;
447 - return $this->current_template_id;
448 - }
449 -
450 - // For normal preview, check template type
438 + if ($nonce_verified && get_post_type($usk_template_id) === Meta::POST_TYPE) {
451 439 $template_type = get_post_meta($usk_template_id, Meta::TEMPLATE_TYPE, true);
452 440 if (!empty($template_type)) {
453 441 $template_data = explode(Builder_Template_Helper::separator(), $template_type);
454 442 if (count($template_data) >= 2 && $template_data[1] === $slug && ($postType === false || $template_data[0] === $postType)) {
455 - $this->current_template_id = (int)$usk_template_id;
443 + $this->current_template_id = $usk_template_id;
456 444 return $this->current_template_id;
457 445 }
458 446 }
459 447 }
@@ -489,8 +477,25 @@
489 477 return $default;
490 478 }
491 479
492 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 + /**
493 498 * Get default product URL with proper error checking
494 499 *
495 500 * @return string
496 501 */
@@ -495,9 +500,9 @@
495 500 * @return string
496 501 */
497 502 protected function get_default_product_url() {
498 503 $products = wc_get_products(['status' => 'publish', 'limit' => 1]);
499 -
504 +
500 505 if (!empty($products) && isset($products[0]) && is_object($products[0])) {
501 506 $product = $products[0];
502 507 if (method_exists($product, 'get_id')) {
503 508 return get_permalink($product->get_id());
@@ -502,9 +507,9 @@
502 507 if (method_exists($product, 'get_id')) {
503 508 return get_permalink($product->get_id());
504 509 }
505 510 }
506 -
511 +
507 512 // Fallback to shop page if no products found
508 513 return get_permalink(wc_get_page_id('shop'));
509 514 }
510 515 }