PluginProbe
ShopBuilder – WooCommerce Builder For Elementor / 3.4.2
ShopBuilder – WooCommerce Builder For Elementor v3.4.2
3.4.2 3.4.1 3.4.0 2.0.1 2.0.2 2.0.3 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.13 2.1.14 2.1.15 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.1.9 2.2.0 2.2.1 2.2.2 All 63 releases
← All changes | app/Controllers/BuilderController.php +144 -70 2.1.113.4.2 View file →
@@ -8,14 +8,14 @@
8 8 namespace RadiusTheme\SB\Controllers;
9 9
10 10 use RadiusTheme\SB\Helpers\Fns;
11 11 use RadiusTheme\SB\Helpers\BuilderFns;
12 +use RadiusTheme\SB\Models\ElementList;
12 13 use RadiusTheme\SB\Traits\SingletonTrait;
13 14 use RadiusTheme\SB\Controllers\Builder\BuilderCpt;
14 15 use RadiusTheme\SB\Controllers\Hooks\BuilderHooks;
15 16 use RadiusTheme\SB\Elementor\Controls\ImageSelectorControl;
16 17 use RadiusTheme\SB\Elementor\Controls\Select2AjaxControl;
17 -use RadiusTheme\SB\Models\ElementList;
18 18
19 19 // Do not allow directly accessing this file.
20 20 if ( ! defined( 'ABSPATH' ) ) {
21 21 exit( 'This script cannot be accessed directly.' );
@@ -54,22 +54,18 @@
54 54 */
55 55 private function __construct() {
56 56 $this->builder_page_id = BuilderFns::builder_page_id_by_page();
57 57 $this->page_edit_with = Fns::page_edit_with( $this->builder_page_id );
58 -
59 58 if ( ! is_admin() ) {
60 59 BuilderHooks::instance();
61 60 }
62 61
63 62 BuilderCpt::instance();
64 -
65 - $this->elementor_init();
66 - $this->both_builder_frontend();
67 -
68 - // add_action( 'elementor/init', [ $this, 'elementor_init' ] );
69 - add_action( 'elementor/editor/before_enqueue_scripts', [ $this, 'editor_scripts' ] );
70 - // add_action( 'template_redirect', [ $this, 'both_builder_frontend' ], 99 );
71 -
63 + if ( defined( 'ELEMENTOR_VERSION' ) ) {
64 + $this->elementor_init();
65 + $this->both_builder_frontend();
66 + add_action( 'elementor/editor/before_enqueue_scripts', [ $this, 'editor_scripts' ] );
67 + }
72 68 // RT Select2 Ajax.
73 69 add_action( 'wp_ajax_rtsb_select2_object_search', [ $this, 'select2_ajax_posts_filter_autocomplete' ] );
74 70 add_action( 'wp_ajax_nopriv_rtsb_select2_object_search', [ $this, 'select2_ajax_posts_filter_autocomplete' ] );
75 71 // Select2 ajax save data.
@@ -92,13 +88,20 @@
92 88 *
93 89 * @return void
94 90 */
95 91 public function elementor_init() {
96 - add_action( 'elementor/elements/categories_registered', [ $this, 'add_elementor_widget_categories' ] );
97 - add_action( 'elementor/widgets/register', [ $this, 'init_widgets' ] );
98 - add_action( 'elementor/controls/register', [ $this, 'init_controls' ] );
99 - add_filter( 'elementor/editor/localize_settings', [ $this, 'promotePremiumWidgets' ] );
100 - add_action( 'elementor/icons_manager/additional_tabs', [ $this, 'rtsb_icons' ] );
92 + add_action(
93 + 'init',
94 + function () {
95 + if ( Fns::should_load_elementor_scripts() ) {
96 + add_action( 'elementor/elements/categories_registered', [ $this, 'add_elementor_widget_categories' ] );
97 + add_action( 'elementor/widgets/register', [ $this, 'init_widgets' ] );
98 + add_action( 'elementor/controls/register', [ $this, 'init_controls' ] );
99 + add_filter( 'elementor/editor/localize_settings', [ $this, 'promotePremiumWidgets' ] );
100 + add_action( 'elementor/icons_manager/additional_tabs', [ $this, 'rtsb_icons' ] );
101 + }
102 + }
103 + );
101 104 }
102 105
103 106 /**
104 107 * Register Category
@@ -112,9 +115,9 @@
112 115 $builder_id = get_queried_object_id();
113 116 $id = Fns::wpml_object_id( $builder_id, BuilderFns::$post_type_tb, 'default' );
114 117 $type = BuilderFns::builder_type( $id );
115 118
116 - if ( $type ) {
119 + if ( $type && 'popup-builder' !== $type ) {
117 120 $builder_type = ! empty( BuilderFns::builder_page_types()[ $type ] ) ? BuilderFns::builder_page_types()[ $type ] : '';
118 121 $builder_type = str_replace( [ 'My Account - ', 'Endpoint: ' ], '', $builder_type );
119 122 $categories['rtsb-shopbuilder'] = [
120 123 'title' => esc_html__( 'ShopBuilder', 'shopbuilder' ) . ' - ' . ucfirst( $builder_type ),
@@ -135,10 +138,10 @@
135 138 $categories,
136 139 array_slice( $other_categories, 1 )
137 140 );
138 141
139 - $set_categories = function ( $categories ) {
140 - $this->categories = $categories;
142 + $set_categories = function ( $categories ) use ( $elements_manager ) {
143 + $elements_manager->categories = $categories;
141 144 };
142 145
143 146 $set_categories->call( $elements_manager, $categories );
144 147 }
@@ -159,10 +162,14 @@
159 162
160 163 /**
161 164 * Init Widgets
162 165 *
163 - * Include widgets files and register them
166 + * Include widget files and register them
164 167 *
168 + * @param object $widgets_manager Widgets Manager.
169 + *
170 + * @return void
171 + *
165 172 * @since 1.0.0
166 173 */
167 174 public function init_widgets( $widgets_manager ) {
168 175
@@ -180,45 +187,24 @@
180 187 continue;
181 188 }
182 189
183 190 if ( ! empty( $element['category'] ) ) {
184 - $widget = false;
185 - $import = apply_filters( 'rtsb_import_status', false );
186 - switch ( $element['category'] ) {
187 - case 'product':
188 - if ( BuilderFns::is_product() || $import ) {
189 - $widget = new $element['base_class']();
190 - }
191 - break;
192 - case 'shop':
193 - if ( BuilderFns::is_archive() || BuilderFns::is_shop() || $import ) {
194 - $widget = new $element['base_class']();
195 - }
196 - break;
197 - case 'archive':
198 - if ( BuilderFns::is_archive() || $import ) {
199 - $widget = new $element['base_class']();
200 - }
201 - break;
202 - case 'cart':
203 - if ( BuilderFns::is_cart() || $import ) {
204 - $widget = new $element['base_class']();
205 - }
206 - break;
207 - case 'checkout':
208 - if ( BuilderFns::is_checkout() || $import ) {
209 - $widget = new $element['base_class']();
210 - }
211 - break;
212 - case 'general':
191 + try {
192 + $widget = false;
193 + $import = apply_filters( 'rtsb_import_status', false );
194 +
195 + if ( $this->should_register_widget( $element['category'], $import ) ) {
213 196 $widget = new $element['base_class']();
214 - break;
215 - default:
216 - }
217 - $widget = apply_filters( 'rtsb/element/list/init/widgets', $widget, $element );
197 + }
218 198
219 - if ( $widget && is_object( $widget ) ) {
220 - $widgets_manager->register( $widget );
199 + $widget = apply_filters( 'rtsb/element/list/init/widgets', $widget, $element );
200 +
201 + if ( $widget && is_object( $widget ) ) {
202 + $widgets_manager->register( $widget );
203 + }
204 + } catch ( \Throwable $e ) {
205 + error_log( 'ShopBuilder widget error (' . $element['base_class'] . '): ' . $e->getMessage() ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
206 + continue;
221 207 }
222 208 }
223 209 }
224 210 }
@@ -223,8 +209,48 @@
223 209 }
224 210 }
225 211
226 212 /**
213 + * Determine whether a widget for the given category should be registered
214 + * in the current request context.
215 + *
216 + * @param string $category Element category.
217 + * @param bool $import Whether an import is in progress.
218 + *
219 + * @return bool
220 + */
221 + protected function should_register_widget( $category, $import ) {
222 + // On the frontend the Elementor Widgets Manager can initialise at an
223 + // unpredictable time — e.g. Elementor Pro's Theme Builder boots it on
224 + // template_redirect — when WooCommerce query conditionals such as
225 + // is_shop()/is_archive() are not yet reliable. Widget registration is
226 + // cached for the whole request, so gating it by those conditionals can
227 + // permanently skip ShopBuilder widgets, leaving the builder template to
228 + // render empty container shells. Register unconditionally on the
229 + // frontend; keep the per-category gate only for the editor widget panel.
230 + if ( ! is_admin() ) {
231 + return true;
232 + }
233 +
234 + switch ( $category ) {
235 + case 'product':
236 + return BuilderFns::is_product() || $import;
237 + case 'shop':
238 + return BuilderFns::is_archive() || BuilderFns::is_shop() || $import;
239 + case 'archive':
240 + return BuilderFns::is_archive() || $import;
241 + case 'cart':
242 + return BuilderFns::is_cart() || $import;
243 + case 'checkout':
244 + return BuilderFns::is_checkout() || $import;
245 + case 'general':
246 + return true;
247 + default:
248 + return false;
249 + }
250 + }
251 +
252 + /**
227 253 * Adding custom icons in Elementor
228 254 *
229 255 * @param array $tabs Tabs.
230 256 *
@@ -289,18 +315,26 @@
289 315 *
290 316 * @since 2.0.0
291 317 */
292 318 if ( did_action( 'elementor/loaded' ) && 'elementor' === $this->page_edit_with ) {
293 - do_action( 'elementor/page_templates/header-footer/before_content' );
319 + do_action( 'elementor/page_templates/header-footer/before_content' ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
294 320 }
295 321
296 322 $parent_class = apply_filters( 'rtsb/builder/content/parent_class', [] );
297 323
298 324 if ( BuilderFns::is_product() ) {
299 - $product_id = Fns::get_prepared_product_id();
325 + $product_id = Fns::get_prepared_product_id();
326 +
327 + // Ensure the WordPress global $product is a valid WC_Product before
328 + // wc_get_product_class() fires the `woocommerce_post_class` filter.
329 + // Some themes (e.g. Blocksy) read the global $product directly inside
330 + // that filter and call methods like get_type() on it, which throws a
331 + // fatal error when the global is still an unresolved string.
332 + $_product = Fns::get_product();
333 +
300 334 $parent_class = wc_get_product_class( $parent_class, $product_id );
301 - if ( function_exists( 'rtwpvs' ) ) {
302 - $_product = Fns::get_product();
335 +
336 + if ( function_exists( 'rtwpvs' ) && $_product instanceof \WC_Product ) {
303 337 if ( $_product->is_type( 'variable' ) ) {
304 338 $parent_class[] = 'rtwpvs-product';
305 339 $beside_label = function_exists( 'rtwpvsp' ) && rtwpvs()->get_option( 'attribute_on_click_behavior' );
306 340 if ( $beside_label ) {
@@ -329,10 +363,34 @@
329 363 if ( BuilderFns::is_checkout() ) {
330 364 if ( ! apply_filters( 'rtsb/multi/step/checkout/widget', true ) ) {
331 365 return;
332 366 }
367 +
368 + $checkout = WC()->checkout();
369 +
370 + // Temporarily remove callbacks that Elementor checkout
371 + // handles via separate widgets to prevent duplicates.
372 + remove_action( 'woocommerce_before_checkout_form', [ Hooks\ActionHooks::class, 'woocommerce_checkout_login_form' ], 10 );
373 + remove_action( 'woocommerce_before_checkout_form', [ Hooks\ActionHooks::class, 'woocommerce_checkout_coupon_form' ], 10 );
374 + remove_action( 'woocommerce_before_checkout_form', [ Fns::class, 'woocommerce_output_all_notices' ], 10 );
375 +
376 + do_action( 'woocommerce_before_checkout_form', $checkout );
377 +
378 + // Restore callbacks for non-Elementor contexts.
379 + add_action( 'woocommerce_before_checkout_form', [ Hooks\ActionHooks::class, 'woocommerce_checkout_login_form' ], 10 );
380 + add_action( 'woocommerce_before_checkout_form', [ Hooks\ActionHooks::class, 'woocommerce_checkout_coupon_form' ], 10 );
381 + add_action( 'woocommerce_before_checkout_form', [ Fns::class, 'woocommerce_output_all_notices' ], 10 );
382 +
383 + // If checkout registration is disabled and not logged in, the user cannot checkout.
384 + if ( ! $checkout->is_registration_enabled() && $checkout->is_registration_required() && ! is_user_logged_in() ) {
385 + echo esc_html( apply_filters( 'woocommerce_checkout_must_be_logged_in_message', __( 'You must be logged in to checkout.', 'shopbuilder' ) ) );
386 + return;
387 + }
333 388 ?>
334 - <form name="checkout" method="post" class="rtsb-woocommerce-checkout checkout" action="<?php echo esc_url( wc_get_checkout_url() ); ?>" enctype="multipart/form-data">
389 + <!--
390 + woocommerce-checkout added for fix issue with payment method ( Revolut Pay )
391 + -->
392 + <form name="checkout" method="post" class="rtsb-woocommerce-checkout woocommerce-checkout checkout" action="<?php echo esc_url( wc_get_checkout_url() ); ?>" enctype="multipart/form-data">
335 393 <?php
336 394 }
337 395 }
338 396
@@ -349,9 +407,9 @@
349 407 *
350 408 * @since 2.0.0
351 409 */
352 410 if ( did_action( 'elementor/loaded' ) && 'elementor' === $this->page_edit_with ) {
353 - do_action( 'elementor/page_templates/header-footer/after_content' );
411 + do_action( 'elementor/page_templates/header-footer/after_content' ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
354 412 }
355 413
356 414 if ( BuilderFns::is_checkout() ) {
357 415 if ( ! apply_filters( 'rtsb/multi/step/checkout/widget', true ) ) {
@@ -359,8 +417,9 @@
359 417 }
360 418 ?>
361 419 </form>
362 420 <?php
421 + do_action( 'woocommerce_after_checkout_form', WC()->checkout() );
363 422 }
364 423 ?>
365 424 </div>
366 425 <!-- After Content end -->
@@ -380,17 +439,20 @@
380 439 $post_type = 'post';
381 440 $source_name = 'post_type';
382 441 $paged = absint( $_POST['page'] ?? 1 );
383 442 if ( ! empty( $_POST['post_type'] ) ) {
443 + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
384 444 $post_type = sanitize_text_field( $_POST['post_type'] );
385 445 }
386 446
387 447 if ( ! empty( $_POST['source_name'] ) ) {
448 + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
388 449 $source_name = sanitize_text_field( $_POST['source_name'] );
389 450 }
390 451
452 + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
391 453 $search = ! empty( $_POST['search'] ) ? sanitize_text_field( $_POST['search'] ) : '';
392 - $results = $post_list = [];
454 + $results = $post_list = []; // phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.Found
393 455 switch ( $source_name ) {
394 456 case 'taxonomy':
395 457 $args = [
396 458 'hide_empty' => false,
@@ -395,13 +457,13 @@
395 457 $args = [
396 458 'hide_empty' => false,
397 459 'orderby' => 'name',
398 460 'order' => 'ASC',
399 - 'search' => $search,
461 + 'name__like' => $search,
400 462 'number' => '5',
401 463 ];
402 464
403 - if ( $post_type !== 'all' ) {
465 + if ( 'all' !== $post_type ) {
404 466 $args['taxonomy'] = $post_type;
405 467 }
406 468
407 469 $post_list = wp_list_pluck( get_terms( $args ), 'name', 'term_id' );
@@ -456,13 +518,14 @@
456 518 if ( empty( $_POST['id'] ) ) {
457 519 wp_send_json_error( [] );
458 520 }
459 521
522 + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
460 523 if ( empty( array_filter( $_POST['id'] ) ) ) {
461 524 wp_send_json_error( [] );
462 525 }
463 526 $ids = array_map( 'intval', $_POST['id'] );
464 - $source_name = ! empty( $_POST['source_name'] ) ? sanitize_text_field( $_POST['source_name'] ) : '';
527 + $source_name = ! empty( $_POST['source_name'] ) ? sanitize_text_field( $_POST['source_name'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
465 528
466 529 switch ( $source_name ) {
467 530 case 'taxonomy':
468 531 $args = [
@@ -471,9 +534,11 @@
471 534 'order' => 'ASC',
472 535 'include' => implode( ',', $ids ),
473 536 ];
474 537
475 - if ( $_POST['post_type'] !== 'all' ) {
538 + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated
539 + if ( 'all' !== $_POST['post_type'] ) {
540 + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
476 541 $args['taxonomy'] = sanitize_text_field( $_POST['post_type'] );
477 542 }
478 543
479 544 $response = wp_list_pluck( get_terms( $args ), 'name', 'term_id' );
@@ -491,9 +556,9 @@
491 556 break;
492 557 default:
493 558 $post_info = get_posts(
494 559 [
495 - 'post_type' => sanitize_text_field( $_POST['post_type'] ),
560 + 'post_type' => sanitize_text_field( $_POST['post_type'] ), // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotValidated
496 561 'include' => implode( ',', $ids ),
497 562 ]
498 563 );
499 564 $response = wp_list_pluck( $post_info, 'post_title', 'ID' );
@@ -509,12 +574,12 @@
509 574
510 575 /**
511 576 * Ajax callback for rt-select2
512 577 *
513 - * @param $post_type
514 - * @param $limit
515 - * @param $search
516 - * @param $paged
578 + * @param string $post_type Post type.
579 + * @param int $limit Limit.
580 + * @param string $search Search.
581 + * @param int $paged Paged.
517 582 *
518 583 * @return array
519 584 */
520 585 public function get_query_data( $post_type = 'any', $limit = 10, $search = '', $paged = 1 ) {
@@ -549,8 +614,17 @@
549 614 }
550 615
551 616 if ( ! empty( $search ) ) {
552 617 $where .= $wpdb->prepare( " AND {$wpdb->posts}.post_title LIKE %s", '%' . esc_sql( $search ) . '%' );
618 + }
619 +
620 + // Add a query for shop_coupon post-type to check 'show on frontend' meta.
621 + if ( 'shop_coupon' === $post_type ) {
622 + $where .= " AND {$wpdb->posts}.ID IN (
623 + SELECT post_id FROM {$wpdb->postmeta}
624 + WHERE meta_key = 'rtsb_show_on_frontend'
625 + AND meta_value = 'on'
626 + )";
553 627 }
554 628
555 629 $query = "select post_title,ID from $wpdb->posts where post_status = 'publish' {$where} {$limit}";
556 630 $results = $wpdb->get_results( $query ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching