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 +184 -91 2.1.03.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
@@ -107,21 +110,29 @@
107 110 *
108 111 * @return void
109 112 */
110 113 public function add_elementor_widget_categories( $elements_manager ) {
111 - $type = BuilderFns::builder_type( get_the_ID() );
112 - $builder_type = ! empty( BuilderFns::builder_page_types()[ $type ] ) ? BuilderFns::builder_page_types()[ $type ] : '';
113 - $builder_type = str_replace( 'My Account - ', '', $builder_type );
114 - $categories['rtsb-shopbuilder'] = [
115 - 'title' => esc_html__( 'ShopBuilder', 'shopbuilder' ) . ' - ' . ucfirst( $builder_type ),
116 - 'icon' => 'fa fa-plug',
117 - ];
118 - $categories['rtsb-shopbuilder-general'] = [
119 - 'title' => esc_html__( 'Shopbuilder - General', 'shopbuilder' ),
120 - 'icon' => 'fa fa-plug',
121 - ];
114 + $categories = [];
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 );
122 118
123 - $categories = apply_filters( 'rtsb_elementor_widgets_category_lists', $categories );
119 + if ( $type && 'popup-builder' !== $type ) {
120 + $builder_type = ! empty( BuilderFns::builder_page_types()[ $type ] ) ? BuilderFns::builder_page_types()[ $type ] : '';
121 + $builder_type = str_replace( [ 'My Account - ', 'Endpoint: ' ], '', $builder_type );
122 + $categories['rtsb-shopbuilder'] = [
123 + 'title' => esc_html__( 'ShopBuilder', 'shopbuilder' ) . ' - ' . ucfirst( $builder_type ),
124 + 'icon' => 'fa fa-plug',
125 + ];
126 + }
127 + if ( 'quick-view' !== $type ) {
128 + $categories['rtsb-shopbuilder-general'] = [
129 + 'title' => esc_html__( 'Shopbuilder - General', 'shopbuilder' ),
130 + 'icon' => 'fa fa-plug',
131 + ];
132 + }
133 + $categories = apply_filters( 'rtsb_elementor_widgets_category_lists', $categories );
134 +
124 135 $other_categories = $elements_manager->get_categories();
125 136 $categories = array_merge(
126 137 array_slice( $other_categories, 0, 1 ),
127 138 $categories,
@@ -127,10 +138,10 @@
127 138 $categories,
128 139 array_slice( $other_categories, 1 )
129 140 );
130 141
131 - $set_categories = function ( $categories ) {
132 - $this->categories = $categories;
142 + $set_categories = function ( $categories ) use ( $elements_manager ) {
143 + $elements_manager->categories = $categories;
133 144 };
134 145
135 146 $set_categories->call( $elements_manager, $categories );
136 147 }
@@ -151,10 +162,14 @@
151 162
152 163 /**
153 164 * Init Widgets
154 165 *
155 - * Include widgets files and register them
166 + * Include widget files and register them
156 167 *
168 + * @param object $widgets_manager Widgets Manager.
169 + *
170 + * @return void
171 + *
157 172 * @since 1.0.0
158 173 */
159 174 public function init_widgets( $widgets_manager ) {
160 175
@@ -172,45 +187,24 @@
172 187 continue;
173 188 }
174 189
175 190 if ( ! empty( $element['category'] ) ) {
176 - $widget = false;
177 - $import = apply_filters( 'rtsb_import_status', false );
178 - switch ( $element['category'] ) {
179 - case 'product':
180 - if ( BuilderFns::is_product() || $import ) {
181 - $widget = new $element['base_class']();
182 - }
183 - break;
184 - case 'shop':
185 - if ( BuilderFns::is_archive() || BuilderFns::is_shop() || $import ) {
186 - $widget = new $element['base_class']();
187 - }
188 - break;
189 - case 'archive':
190 - if ( BuilderFns::is_archive() || $import ) {
191 - $widget = new $element['base_class']();
192 - }
193 - break;
194 - case 'cart':
195 - if ( BuilderFns::is_cart() || $import ) {
196 - $widget = new $element['base_class']();
197 - }
198 - break;
199 - case 'checkout':
200 - if ( BuilderFns::is_checkout() || $import ) {
201 - $widget = new $element['base_class']();
202 - }
203 - break;
204 - 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 ) ) {
205 196 $widget = new $element['base_class']();
206 - break;
207 - default:
208 - }
209 - $widget = apply_filters( 'rtsb/element/list/init/widgets', $widget, $element );
197 + }
210 198
211 - if ( $widget && is_object( $widget ) ) {
212 - $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;
213 207 }
214 208 }
215 209 }
216 210 }
@@ -215,8 +209,36 @@
215 209 }
216 210 }
217 211
218 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 + /**
219 241 * Adding custom icons in Elementor
220 242 *
221 243 * @param array $tabs Tabs.
222 244 *
@@ -222,9 +244,9 @@
222 244 *
223 245 * @return array
224 246 */
225 247 public function rtsb_icons( $tabs = [] ) {
226 - $custom_icons = $this->get_icons();
248 + $custom_icons = Fns::get_custom_icon_names();
227 249
228 250 $tabs['shopbuilder-icons'] = [
229 251 'name' => 'rtsb-fonts',
230 252 'label' => esc_html__( 'ShopBuilder Icons', 'shopbuilder' ),
@@ -281,16 +303,34 @@
281 303 *
282 304 * @since 2.0.0
283 305 */
284 306 if ( did_action( 'elementor/loaded' ) && 'elementor' === $this->page_edit_with ) {
285 - 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
286 308 }
287 309
288 310 $parent_class = apply_filters( 'rtsb/builder/content/parent_class', [] );
289 311
290 312 if ( BuilderFns::is_product() ) {
291 - $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 +
292 322 $parent_class = wc_get_product_class( $parent_class, $product_id );
323 +
324 + if ( function_exists( 'rtwpvs' ) && $_product instanceof \WC_Product ) {
325 + if ( $_product->is_type( 'variable' ) ) {
326 + $parent_class[] = 'rtwpvs-product';
327 + $beside_label = function_exists( 'rtwpvsp' ) && rtwpvs()->get_option( 'attribute_on_click_behavior' );
328 + if ( $beside_label ) {
329 + $parent_class[] = 'rtwpvs-selected-term-beside-label';
330 + }
331 + }
332 + }
293 333 }
294 334
295 335 if ( BuilderFns::is_shop() ) {
296 336 $parent_class[] = 'shop';
@@ -306,10 +346,39 @@
306 346
307 347 ?>
308 348 <!-- Before Content start -->
309 349 <div class="<?php echo esc_attr( implode( ' ', $parent_class ) ); ?>">
310 - <?php if ( BuilderFns::is_checkout() ) { ?>
311 - <form name="checkout" method="post" class="rtsb-woocommerce-checkout checkout" action="<?php echo esc_url( wc_get_checkout_url() ); ?>" enctype="multipart/form-data">
350 + <?php
351 + if ( BuilderFns::is_checkout() ) {
352 + if ( ! apply_filters( 'rtsb/multi/step/checkout/widget', true ) ) {
353 + return;
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 + }
376 + ?>
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">
312 381 <?php
313 382 }
314 383 }
315 384
@@ -326,15 +395,19 @@
326 395 *
327 396 * @since 2.0.0
328 397 */
329 398 if ( did_action( 'elementor/loaded' ) && 'elementor' === $this->page_edit_with ) {
330 - 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
331 400 }
332 401
333 402 if ( BuilderFns::is_checkout() ) {
403 + if ( ! apply_filters( 'rtsb/multi/step/checkout/widget', true ) ) {
404 + return;
405 + }
334 406 ?>
335 407 </form>
336 408 <?php
409 + do_action( 'woocommerce_after_checkout_form', WC()->checkout() );
337 410 }
338 411 ?>
339 412 </div>
340 413 <!-- After Content end -->
@@ -346,24 +419,28 @@
346 419 *
347 420 * @return void
348 421 */
349 422 public function select2_ajax_posts_filter_autocomplete() {
350 -
423 + if ( ! wp_verify_nonce( Fns::get_nonce(), rtsb()->nonceText ) ) {
424 + wp_send_json_error( [] );
425 + }
351 426 $query_per_page = 15;
352 427 $post_type = 'post';
353 428 $source_name = 'post_type';
354 - $paged = $_POST['page'] ?? 1;
355 -
429 + $paged = absint( $_POST['page'] ?? 1 );
356 430 if ( ! empty( $_POST['post_type'] ) ) {
431 + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
357 432 $post_type = sanitize_text_field( $_POST['post_type'] );
358 433 }
359 434
360 435 if ( ! empty( $_POST['source_name'] ) ) {
436 + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
361 437 $source_name = sanitize_text_field( $_POST['source_name'] );
362 438 }
363 439
440 + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
364 441 $search = ! empty( $_POST['search'] ) ? sanitize_text_field( $_POST['search'] ) : '';
365 - $results = $post_list = [];
442 + $results = $post_list = []; // phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.Found
366 443 switch ( $source_name ) {
367 444 case 'taxonomy':
368 445 $args = [
369 446 'hide_empty' => false,
@@ -368,13 +445,13 @@
368 445 $args = [
369 446 'hide_empty' => false,
370 447 'orderby' => 'name',
371 448 'order' => 'ASC',
372 - 'search' => $search,
449 + 'name__like' => $search,
373 450 'number' => '5',
374 451 ];
375 452
376 - if ( $post_type !== 'all' ) {
453 + if ( 'all' !== $post_type ) {
377 454 $args['taxonomy'] = $post_type;
378 455 }
379 456
380 457 $post_list = wp_list_pluck( get_terms( $args ), 'name', 'term_id' );
@@ -421,17 +498,22 @@
421 498 * @return void
422 499 */
423 500 public function select2_ajax_get_posts_value_titles() {
424 501
502 + if ( ! wp_verify_nonce( Fns::get_nonce(), rtsb()->nonceText ) ) {
503 + wp_send_json_error( [] );
504 + }
505 +
425 506 if ( empty( $_POST['id'] ) ) {
426 507 wp_send_json_error( [] );
427 508 }
428 509
510 + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
429 511 if ( empty( array_filter( $_POST['id'] ) ) ) {
430 512 wp_send_json_error( [] );
431 513 }
432 514 $ids = array_map( 'intval', $_POST['id'] );
433 - $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
434 516
435 517 switch ( $source_name ) {
436 518 case 'taxonomy':
437 519 $args = [
@@ -440,9 +522,11 @@
440 522 'order' => 'ASC',
441 523 'include' => implode( ',', $ids ),
442 524 ];
443 525
444 - 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
445 529 $args['taxonomy'] = sanitize_text_field( $_POST['post_type'] );
446 530 }
447 531
448 532 $response = wp_list_pluck( get_terms( $args ), 'name', 'term_id' );
@@ -460,9 +544,9 @@
460 544 break;
461 545 default:
462 546 $post_info = get_posts(
463 547 [
464 - '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
465 549 'include' => implode( ',', $ids ),
466 550 ]
467 551 );
468 552 $response = wp_list_pluck( $post_info, 'post_title', 'ID' );
@@ -478,12 +562,12 @@
478 562
479 563 /**
480 564 * Ajax callback for rt-select2
481 565 *
482 - * @param $post_type
483 - * @param $limit
484 - * @param $search
485 - * @param $paged
566 + * @param string $post_type Post type.
567 + * @param int $limit Limit.
568 + * @param string $search Search.
569 + * @param int $paged Paged.
486 570 *
487 571 * @return array
488 572 */
489 573 public function get_query_data( $post_type = 'any', $limit = 10, $search = '', $paged = 1 ) {
@@ -520,10 +604,19 @@
520 604 if ( ! empty( $search ) ) {
521 605 $where .= $wpdb->prepare( " AND {$wpdb->posts}.post_title LIKE %s", '%' . esc_sql( $search ) . '%' );
522 606 }
523 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 +
524 617 $query = "select post_title,ID from $wpdb->posts where post_status = 'publish' {$where} {$limit}";
525 - $results = $wpdb->get_results( $query );
618 + $results = $wpdb->get_results( $query ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching
526 619
527 620 if ( ! empty( $results ) ) {
528 621 foreach ( $results as $row ) {
529 622 $data[ $row->ID ] = $row->post_title . ' [#' . $row->ID . ']';
@@ -554,10 +647,10 @@
554 647
555 648 $pro_widgets = [
556 649 [
557 650 'name' => 'rtsb-ajax-product-filters',
558 - 'title' => esc_html__( 'Ajax Product Filters', 'testimonial-slider-showcase' ),
559 - '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' ),
560 653 'icon' => 'rtsb-el-custom rtsb-element icon-rtsb-ajax-product-filters rtsb-promotional-element',
561 654 'categories' => '[ "rtsb-shopbuilder" ]',
562 655 ],
563 656 ];