PluginProbe
ShopBuilder – WooCommerce Builder For Elementor / 3.4.0
ShopBuilder – WooCommerce Builder For Elementor v3.4.0
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 +146 -80 2.1.93.4.0 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,36 @@
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 + switch ( $category ) {
223 + case 'product':
224 + return BuilderFns::is_product() || $import;
225 + case 'shop':
226 + return BuilderFns::is_archive() || BuilderFns::is_shop() || $import;
227 + case 'archive':
228 + return BuilderFns::is_archive() || $import;
229 + case 'cart':
230 + return BuilderFns::is_cart() || $import;
231 + case 'checkout':
232 + return BuilderFns::is_checkout() || $import;
233 + case 'general':
234 + return true;
235 + default:
236 + return false;
237 + }
238 + }
239 +
240 + /**
223 241 * Adding custom icons in Elementor
224 242 *
225 243 * @param array $tabs Tabs.
226 244 *
@@ -226,9 +244,9 @@
226 244 *
227 245 * @return array
228 246 */
229 247 public function rtsb_icons( $tabs = [] ) {
230 - $custom_icons = $this->get_icons();
248 + $custom_icons = Fns::get_custom_icon_names();
231 249
232 250 $tabs['shopbuilder-icons'] = [
233 251 'name' => 'rtsb-fonts',
234 252 'label' => esc_html__( 'ShopBuilder Icons', 'shopbuilder' ),
@@ -285,18 +303,26 @@
285 303 *
286 304 * @since 2.0.0
287 305 */
288 306 if ( did_action( 'elementor/loaded' ) && 'elementor' === $this->page_edit_with ) {
289 - do_action( 'elementor/page_templates/header-footer/before_content' );
307 + do_action( 'elementor/page_templates/header-footer/before_content' ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
290 308 }
291 309
292 310 $parent_class = apply_filters( 'rtsb/builder/content/parent_class', [] );
293 311
294 312 if ( BuilderFns::is_product() ) {
295 - $product_id = Fns::get_prepared_product_id();
313 + $product_id = Fns::get_prepared_product_id();
314 +
315 + // Ensure the WordPress global $product is a valid WC_Product before
316 + // wc_get_product_class() fires the `woocommerce_post_class` filter.
317 + // Some themes (e.g. Blocksy) read the global $product directly inside
318 + // that filter and call methods like get_type() on it, which throws a
319 + // fatal error when the global is still an unresolved string.
320 + $_product = Fns::get_product();
321 +
296 322 $parent_class = wc_get_product_class( $parent_class, $product_id );
297 - if ( function_exists( 'rtwpvs' ) ) {
298 - $_product = Fns::get_product();
323 +
324 + if ( function_exists( 'rtwpvs' ) && $_product instanceof \WC_Product ) {
299 325 if ( $_product->is_type( 'variable' ) ) {
300 326 $parent_class[] = 'rtwpvs-product';
301 327 $beside_label = function_exists( 'rtwpvsp' ) && rtwpvs()->get_option( 'attribute_on_click_behavior' );
302 328 if ( $beside_label ) {
@@ -325,10 +351,34 @@
325 351 if ( BuilderFns::is_checkout() ) {
326 352 if ( ! apply_filters( 'rtsb/multi/step/checkout/widget', true ) ) {
327 353 return;
328 354 }
355 +
356 + $checkout = WC()->checkout();
357 +
358 + // Temporarily remove callbacks that Elementor checkout
359 + // handles via separate widgets to prevent duplicates.
360 + remove_action( 'woocommerce_before_checkout_form', [ Hooks\ActionHooks::class, 'woocommerce_checkout_login_form' ], 10 );
361 + remove_action( 'woocommerce_before_checkout_form', [ Hooks\ActionHooks::class, 'woocommerce_checkout_coupon_form' ], 10 );
362 + remove_action( 'woocommerce_before_checkout_form', [ Fns::class, 'woocommerce_output_all_notices' ], 10 );
363 +
364 + do_action( 'woocommerce_before_checkout_form', $checkout );
365 +
366 + // Restore callbacks for non-Elementor contexts.
367 + add_action( 'woocommerce_before_checkout_form', [ Hooks\ActionHooks::class, 'woocommerce_checkout_login_form' ], 10 );
368 + add_action( 'woocommerce_before_checkout_form', [ Hooks\ActionHooks::class, 'woocommerce_checkout_coupon_form' ], 10 );
369 + add_action( 'woocommerce_before_checkout_form', [ Fns::class, 'woocommerce_output_all_notices' ], 10 );
370 +
371 + // If checkout registration is disabled and not logged in, the user cannot checkout.
372 + if ( ! $checkout->is_registration_enabled() && $checkout->is_registration_required() && ! is_user_logged_in() ) {
373 + echo esc_html( apply_filters( 'woocommerce_checkout_must_be_logged_in_message', __( 'You must be logged in to checkout.', 'shopbuilder' ) ) );
374 + return;
375 + }
329 376 ?>
330 - <form name="checkout" method="post" class="rtsb-woocommerce-checkout checkout" action="<?php echo esc_url( wc_get_checkout_url() ); ?>" enctype="multipart/form-data">
377 + <!--
378 + woocommerce-checkout added for fix issue with payment method ( Revolut Pay )
379 + -->
380 + <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 381 <?php
332 382 }
333 383 }
334 384
@@ -345,9 +395,9 @@
345 395 *
346 396 * @since 2.0.0
347 397 */
348 398 if ( did_action( 'elementor/loaded' ) && 'elementor' === $this->page_edit_with ) {
349 - do_action( 'elementor/page_templates/header-footer/after_content' );
399 + do_action( 'elementor/page_templates/header-footer/after_content' ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
350 400 }
351 401
352 402 if ( BuilderFns::is_checkout() ) {
353 403 if ( ! apply_filters( 'rtsb/multi/step/checkout/widget', true ) ) {
@@ -355,8 +405,9 @@
355 405 }
356 406 ?>
357 407 </form>
358 408 <?php
409 + do_action( 'woocommerce_after_checkout_form', WC()->checkout() );
359 410 }
360 411 ?>
361 412 </div>
362 413 <!-- After Content end -->
@@ -376,17 +427,20 @@
376 427 $post_type = 'post';
377 428 $source_name = 'post_type';
378 429 $paged = absint( $_POST['page'] ?? 1 );
379 430 if ( ! empty( $_POST['post_type'] ) ) {
431 + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
380 432 $post_type = sanitize_text_field( $_POST['post_type'] );
381 433 }
382 434
383 435 if ( ! empty( $_POST['source_name'] ) ) {
436 + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
384 437 $source_name = sanitize_text_field( $_POST['source_name'] );
385 438 }
386 439
440 + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
387 441 $search = ! empty( $_POST['search'] ) ? sanitize_text_field( $_POST['search'] ) : '';
388 - $results = $post_list = [];
442 + $results = $post_list = []; // phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.Found
389 443 switch ( $source_name ) {
390 444 case 'taxonomy':
391 445 $args = [
392 446 'hide_empty' => false,
@@ -391,13 +445,13 @@
391 445 $args = [
392 446 'hide_empty' => false,
393 447 'orderby' => 'name',
394 448 'order' => 'ASC',
395 - 'search' => $search,
449 + 'name__like' => $search,
396 450 'number' => '5',
397 451 ];
398 452
399 - if ( $post_type !== 'all' ) {
453 + if ( 'all' !== $post_type ) {
400 454 $args['taxonomy'] = $post_type;
401 455 }
402 456
403 457 $post_list = wp_list_pluck( get_terms( $args ), 'name', 'term_id' );
@@ -452,13 +506,14 @@
452 506 if ( empty( $_POST['id'] ) ) {
453 507 wp_send_json_error( [] );
454 508 }
455 509
510 + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
456 511 if ( empty( array_filter( $_POST['id'] ) ) ) {
457 512 wp_send_json_error( [] );
458 513 }
459 514 $ids = array_map( 'intval', $_POST['id'] );
460 - $source_name = ! empty( $_POST['source_name'] ) ? sanitize_text_field( $_POST['source_name'] ) : '';
515 + $source_name = ! empty( $_POST['source_name'] ) ? sanitize_text_field( $_POST['source_name'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
461 516
462 517 switch ( $source_name ) {
463 518 case 'taxonomy':
464 519 $args = [
@@ -467,9 +522,11 @@
467 522 'order' => 'ASC',
468 523 'include' => implode( ',', $ids ),
469 524 ];
470 525
471 - if ( $_POST['post_type'] !== 'all' ) {
526 + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated
527 + if ( 'all' !== $_POST['post_type'] ) {
528 + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
472 529 $args['taxonomy'] = sanitize_text_field( $_POST['post_type'] );
473 530 }
474 531
475 532 $response = wp_list_pluck( get_terms( $args ), 'name', 'term_id' );
@@ -487,9 +544,9 @@
487 544 break;
488 545 default:
489 546 $post_info = get_posts(
490 547 [
491 - 'post_type' => sanitize_text_field( $_POST['post_type'] ),
548 + 'post_type' => sanitize_text_field( $_POST['post_type'] ), // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotValidated
492 549 'include' => implode( ',', $ids ),
493 550 ]
494 551 );
495 552 $response = wp_list_pluck( $post_info, 'post_title', 'ID' );
@@ -505,12 +562,12 @@
505 562
506 563 /**
507 564 * Ajax callback for rt-select2
508 565 *
509 - * @param $post_type
510 - * @param $limit
511 - * @param $search
512 - * @param $paged
566 + * @param string $post_type Post type.
567 + * @param int $limit Limit.
568 + * @param string $search Search.
569 + * @param int $paged Paged.
513 570 *
514 571 * @return array
515 572 */
516 573 public function get_query_data( $post_type = 'any', $limit = 10, $search = '', $paged = 1 ) {
@@ -547,8 +604,17 @@
547 604 if ( ! empty( $search ) ) {
548 605 $where .= $wpdb->prepare( " AND {$wpdb->posts}.post_title LIKE %s", '%' . esc_sql( $search ) . '%' );
549 606 }
550 607
608 + // Add a query for shop_coupon post-type to check 'show on frontend' meta.
609 + if ( 'shop_coupon' === $post_type ) {
610 + $where .= " AND {$wpdb->posts}.ID IN (
611 + SELECT post_id FROM {$wpdb->postmeta}
612 + WHERE meta_key = 'rtsb_show_on_frontend'
613 + AND meta_value = 'on'
614 + )";
615 + }
616 +
551 617 $query = "select post_title,ID from $wpdb->posts where post_status = 'publish' {$where} {$limit}";
552 618 $results = $wpdb->get_results( $query ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching
553 619
554 620 if ( ! empty( $results ) ) {
@@ -581,10 +647,10 @@
581 647
582 648 $pro_widgets = [
583 649 [
584 650 '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' ),
651 + 'title' => esc_html__( 'Ajax Product Filters', 'shopbuilder' ),
652 + 'description' => esc_html__( 'Ajax Product Filters', 'shopbuilder' ),
587 653 'icon' => 'rtsb-el-custom rtsb-element icon-rtsb-ajax-product-filters rtsb-promotional-element',
588 654 'categories' => '[ "rtsb-shopbuilder" ]',
589 655 ],
590 656 ];