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 +158 -80 2.1.93.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,28 +54,24 @@
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 - add_action( 'wp_ajax_rt_select2_object_search', [ $this, 'select2_ajax_posts_filter_autocomplete' ] );
74 - add_action( 'wp_ajax_nopriv_rt_select2_object_search', [ $this, 'select2_ajax_posts_filter_autocomplete' ] );
69 + add_action( 'wp_ajax_rtsb_select2_object_search', [ $this, 'select2_ajax_posts_filter_autocomplete' ] );
70 + add_action( 'wp_ajax_nopriv_rtsb_select2_object_search', [ $this, 'select2_ajax_posts_filter_autocomplete' ] );
75 71 // Select2 ajax save data.
76 - add_action( 'wp_ajax_rt_select2_get_title', [ $this, 'select2_ajax_get_posts_value_titles' ] );
77 - add_action( 'wp_ajax_nopriv_rt_select2_get_title', [ $this, 'select2_ajax_get_posts_value_titles' ] );
72 + add_action( 'wp_ajax_rtsb_select2_get_title', [ $this, 'select2_ajax_get_posts_value_titles' ] );
73 + add_action( 'wp_ajax_nopriv_rtsb_select2_get_title', [ $this, 'select2_ajax_get_posts_value_titles' ] );
78 74 }
79 75
80 76 /**
81 77 * Apply for frontend.
@@ -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
@@ -108,12 +111,15 @@
108 111 * @return void
109 112 */
110 113 public function add_elementor_widget_categories( $elements_manager ) {
111 114 $categories = [];
112 - $type = BuilderFns::builder_type( get_the_ID() );
113 - if ( $type ) {
115 + $builder_id = get_queried_object_id();
116 + $id = Fns::wpml_object_id( $builder_id, BuilderFns::$post_type_tb, 'default' );
117 + $type = BuilderFns::builder_type( $id );
118 +
119 + if ( $type && 'popup-builder' !== $type ) {
114 120 $builder_type = ! empty( BuilderFns::builder_page_types()[ $type ] ) ? BuilderFns::builder_page_types()[ $type ] : '';
115 - $builder_type = str_replace( 'My Account - ', '', $builder_type );
121 + $builder_type = str_replace( [ 'My Account - ', 'Endpoint: ' ], '', $builder_type );
116 122 $categories['rtsb-shopbuilder'] = [
117 123 'title' => esc_html__( 'ShopBuilder', 'shopbuilder' ) . ' - ' . ucfirst( $builder_type ),
118 124 'icon' => 'fa fa-plug',
119 125 ];
@@ -123,9 +129,10 @@
123 129 'title' => esc_html__( 'Shopbuilder - General', 'shopbuilder' ),
124 130 'icon' => 'fa fa-plug',
125 131 ];
126 132 }
127 - $categories = apply_filters( 'rtsb_elementor_widgets_category_lists', $categories );
133 + $categories = apply_filters( 'rtsb_elementor_widgets_category_lists', $categories );
134 +
128 135 $other_categories = $elements_manager->get_categories();
129 136 $categories = array_merge(
130 137 array_slice( $other_categories, 0, 1 ),
131 138 $categories,
@@ -131,10 +138,10 @@
131 138 $categories,
132 139 array_slice( $other_categories, 1 )
133 140 );
134 141
135 - $set_categories = function ( $categories ) {
136 - $this->categories = $categories;
142 + $set_categories = function ( $categories ) use ( $elements_manager ) {
143 + $elements_manager->categories = $categories;
137 144 };
138 145
139 146 $set_categories->call( $elements_manager, $categories );
140 147 }
@@ -155,10 +162,14 @@
155 162
156 163 /**
157 164 * Init Widgets
158 165 *
159 - * Include widgets files and register them
166 + * Include widget files and register them
160 167 *
168 + * @param object $widgets_manager Widgets Manager.
169 + *
170 + * @return void
171 + *
161 172 * @since 1.0.0
162 173 */
163 174 public function init_widgets( $widgets_manager ) {
164 175
@@ -176,45 +187,24 @@
176 187 continue;
177 188 }
178 189
179 190 if ( ! empty( $element['category'] ) ) {
180 - $widget = false;
181 - $import = apply_filters( 'rtsb_import_status', false );
182 - switch ( $element['category'] ) {
183 - case 'product':
184 - if ( BuilderFns::is_product() || $import ) {
185 - $widget = new $element['base_class']();
186 - }
187 - break;
188 - case 'shop':
189 - if ( BuilderFns::is_archive() || BuilderFns::is_shop() || $import ) {
190 - $widget = new $element['base_class']();
191 - }
192 - break;
193 - case 'archive':
194 - if ( BuilderFns::is_archive() || $import ) {
195 - $widget = new $element['base_class']();
196 - }
197 - break;
198 - case 'cart':
199 - if ( BuilderFns::is_cart() || $import ) {
200 - $widget = new $element['base_class']();
201 - }
202 - break;
203 - case 'checkout':
204 - if ( BuilderFns::is_checkout() || $import ) {
205 - $widget = new $element['base_class']();
206 - }
207 - break;
208 - 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 ) ) {
209 196 $widget = new $element['base_class']();
210 - break;
211 - default:
212 - }
213 - $widget = apply_filters( 'rtsb/element/list/init/widgets', $widget, $element );
197 + }
214 198
215 - if ( $widget && is_object( $widget ) ) {
216 - $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;
217 207 }
218 208 }
219 209 }
220 210 }
@@ -219,8 +209,48 @@
219 209 }
220 210 }
221 211
222 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 + /**
223 253 * Adding custom icons in Elementor
224 254 *
225 255 * @param array $tabs Tabs.
226 256 *
@@ -226,9 +256,9 @@
226 256 *
227 257 * @return array
228 258 */
229 259 public function rtsb_icons( $tabs = [] ) {
230 - $custom_icons = $this->get_icons();
260 + $custom_icons = Fns::get_custom_icon_names();
231 261
232 262 $tabs['shopbuilder-icons'] = [
233 263 'name' => 'rtsb-fonts',
234 264 'label' => esc_html__( 'ShopBuilder Icons', 'shopbuilder' ),
@@ -285,18 +315,26 @@
285 315 *
286 316 * @since 2.0.0
287 317 */
288 318 if ( did_action( 'elementor/loaded' ) && 'elementor' === $this->page_edit_with ) {
289 - 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
290 320 }
291 321
292 322 $parent_class = apply_filters( 'rtsb/builder/content/parent_class', [] );
293 323
294 324 if ( BuilderFns::is_product() ) {
295 - $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 +
296 334 $parent_class = wc_get_product_class( $parent_class, $product_id );
297 - if ( function_exists( 'rtwpvs' ) ) {
298 - $_product = Fns::get_product();
335 +
336 + if ( function_exists( 'rtwpvs' ) && $_product instanceof \WC_Product ) {
299 337 if ( $_product->is_type( 'variable' ) ) {
300 338 $parent_class[] = 'rtwpvs-product';
301 339 $beside_label = function_exists( 'rtwpvsp' ) && rtwpvs()->get_option( 'attribute_on_click_behavior' );
302 340 if ( $beside_label ) {
@@ -325,10 +363,34 @@
325 363 if ( BuilderFns::is_checkout() ) {
326 364 if ( ! apply_filters( 'rtsb/multi/step/checkout/widget', true ) ) {
327 365 return;
328 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 + }
329 388 ?>
330 - <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">
331 393 <?php
332 394 }
333 395 }
334 396
@@ -345,9 +407,9 @@
345 407 *
346 408 * @since 2.0.0
347 409 */
348 410 if ( did_action( 'elementor/loaded' ) && 'elementor' === $this->page_edit_with ) {
349 - 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
350 412 }
351 413
352 414 if ( BuilderFns::is_checkout() ) {
353 415 if ( ! apply_filters( 'rtsb/multi/step/checkout/widget', true ) ) {
@@ -355,8 +417,9 @@
355 417 }
356 418 ?>
357 419 </form>
358 420 <?php
421 + do_action( 'woocommerce_after_checkout_form', WC()->checkout() );
359 422 }
360 423 ?>
361 424 </div>
362 425 <!-- After Content end -->
@@ -376,17 +439,20 @@
376 439 $post_type = 'post';
377 440 $source_name = 'post_type';
378 441 $paged = absint( $_POST['page'] ?? 1 );
379 442 if ( ! empty( $_POST['post_type'] ) ) {
443 + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
380 444 $post_type = sanitize_text_field( $_POST['post_type'] );
381 445 }
382 446
383 447 if ( ! empty( $_POST['source_name'] ) ) {
448 + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
384 449 $source_name = sanitize_text_field( $_POST['source_name'] );
385 450 }
386 451
452 + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
387 453 $search = ! empty( $_POST['search'] ) ? sanitize_text_field( $_POST['search'] ) : '';
388 - $results = $post_list = [];
454 + $results = $post_list = []; // phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.Found
389 455 switch ( $source_name ) {
390 456 case 'taxonomy':
391 457 $args = [
392 458 'hide_empty' => false,
@@ -391,13 +457,13 @@
391 457 $args = [
392 458 'hide_empty' => false,
393 459 'orderby' => 'name',
394 460 'order' => 'ASC',
395 - 'search' => $search,
461 + 'name__like' => $search,
396 462 'number' => '5',
397 463 ];
398 464
399 - if ( $post_type !== 'all' ) {
465 + if ( 'all' !== $post_type ) {
400 466 $args['taxonomy'] = $post_type;
401 467 }
402 468
403 469 $post_list = wp_list_pluck( get_terms( $args ), 'name', 'term_id' );
@@ -452,13 +518,14 @@
452 518 if ( empty( $_POST['id'] ) ) {
453 519 wp_send_json_error( [] );
454 520 }
455 521
522 + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
456 523 if ( empty( array_filter( $_POST['id'] ) ) ) {
457 524 wp_send_json_error( [] );
458 525 }
459 526 $ids = array_map( 'intval', $_POST['id'] );
460 - $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
461 528
462 529 switch ( $source_name ) {
463 530 case 'taxonomy':
464 531 $args = [
@@ -467,9 +534,11 @@
467 534 'order' => 'ASC',
468 535 'include' => implode( ',', $ids ),
469 536 ];
470 537
471 - 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
472 541 $args['taxonomy'] = sanitize_text_field( $_POST['post_type'] );
473 542 }
474 543
475 544 $response = wp_list_pluck( get_terms( $args ), 'name', 'term_id' );
@@ -487,9 +556,9 @@
487 556 break;
488 557 default:
489 558 $post_info = get_posts(
490 559 [
491 - '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
492 561 'include' => implode( ',', $ids ),
493 562 ]
494 563 );
495 564 $response = wp_list_pluck( $post_info, 'post_title', 'ID' );
@@ -505,12 +574,12 @@
505 574
506 575 /**
507 576 * Ajax callback for rt-select2
508 577 *
509 - * @param $post_type
510 - * @param $limit
511 - * @param $search
512 - * @param $paged
578 + * @param string $post_type Post type.
579 + * @param int $limit Limit.
580 + * @param string $search Search.
581 + * @param int $paged Paged.
513 582 *
514 583 * @return array
515 584 */
516 585 public function get_query_data( $post_type = 'any', $limit = 10, $search = '', $paged = 1 ) {
@@ -547,8 +616,17 @@
547 616 if ( ! empty( $search ) ) {
548 617 $where .= $wpdb->prepare( " AND {$wpdb->posts}.post_title LIKE %s", '%' . esc_sql( $search ) . '%' );
549 618 }
550 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 + )";
627 + }
628 +
551 629 $query = "select post_title,ID from $wpdb->posts where post_status = 'publish' {$where} {$limit}";
552 630 $results = $wpdb->get_results( $query ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching
553 631
554 632 if ( ! empty( $results ) ) {
@@ -581,10 +659,10 @@
581 659
582 660 $pro_widgets = [
583 661 [
584 662 'name' => 'rtsb-ajax-product-filters',
585 - 'title' => esc_html__( 'Ajax Product Filters', 'testimonial-slider-showcase' ),
586 - 'description' => esc_html__( 'Ajax Product Filters', 'testimonial-slider-showcase' ),
663 + 'title' => esc_html__( 'Ajax Product Filters', 'shopbuilder' ),
664 + 'description' => esc_html__( 'Ajax Product Filters', 'shopbuilder' ),
587 665 'icon' => 'rtsb-el-custom rtsb-element icon-rtsb-ajax-product-filters rtsb-promotional-element',
588 666 'categories' => '[ "rtsb-shopbuilder" ]',
589 667 ],
590 668 ];