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 +175 -91 2.1.13.4.0 View file →
@@ -8,15 +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 -use RadiusTheme\SBPRO\Helpers\FnsPro;
19 18
20 19 // Do not allow directly accessing this file.
21 20 if ( ! defined( 'ABSPATH' ) ) {
22 21 exit( 'This script cannot be accessed directly.' );
@@ -55,28 +54,24 @@
55 54 */
56 55 private function __construct() {
57 56 $this->builder_page_id = BuilderFns::builder_page_id_by_page();
58 57 $this->page_edit_with = Fns::page_edit_with( $this->builder_page_id );
59 -
60 58 if ( ! is_admin() ) {
61 59 BuilderHooks::instance();
62 60 }
63 61
64 62 BuilderCpt::instance();
65 -
66 - $this->elementor_init();
67 - $this->both_builder_frontend();
68 -
69 - // add_action( 'elementor/init', [ $this, 'elementor_init' ] );
70 - add_action( 'elementor/editor/before_enqueue_scripts', [ $this, 'editor_scripts' ] );
71 - // add_action( 'template_redirect', [ $this, 'both_builder_frontend' ], 99 );
72 -
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 + }
73 68 // RT Select2 Ajax.
74 - add_action( 'wp_ajax_rt_select2_object_search', [ $this, 'select2_ajax_posts_filter_autocomplete' ] );
75 - 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' ] );
76 71 // Select2 ajax save data.
77 - add_action( 'wp_ajax_rt_select2_get_title', [ $this, 'select2_ajax_get_posts_value_titles' ] );
78 - 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' ] );
79 74 }
80 75
81 76 /**
82 77 * Apply for frontend.
@@ -93,13 +88,20 @@
93 88 *
94 89 * @return void
95 90 */
96 91 public function elementor_init() {
97 - add_action( 'elementor/elements/categories_registered', [ $this, 'add_elementor_widget_categories' ] );
98 - add_action( 'elementor/widgets/register', [ $this, 'init_widgets' ] );
99 - add_action( 'elementor/controls/register', [ $this, 'init_controls' ] );
100 - add_filter( 'elementor/editor/localize_settings', [ $this, 'promotePremiumWidgets' ] );
101 - 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 + );
102 104 }
103 105
104 106 /**
105 107 * Register Category
@@ -108,21 +110,29 @@
108 110 *
109 111 * @return void
110 112 */
111 113 public function add_elementor_widget_categories( $elements_manager ) {
112 - $type = BuilderFns::builder_type( get_the_ID() );
113 - $builder_type = ! empty( BuilderFns::builder_page_types()[ $type ] ) ? BuilderFns::builder_page_types()[ $type ] : '';
114 - $builder_type = str_replace( 'My Account - ', '', $builder_type );
115 - $categories['rtsb-shopbuilder'] = [
116 - 'title' => esc_html__( 'ShopBuilder', 'shopbuilder' ) . ' - ' . ucfirst( $builder_type ),
117 - 'icon' => 'fa fa-plug',
118 - ];
119 - $categories['rtsb-shopbuilder-general'] = [
120 - 'title' => esc_html__( 'Shopbuilder - General', 'shopbuilder' ),
121 - 'icon' => 'fa fa-plug',
122 - ];
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 );
123 118
124 - $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 +
125 135 $other_categories = $elements_manager->get_categories();
126 136 $categories = array_merge(
127 137 array_slice( $other_categories, 0, 1 ),
128 138 $categories,
@@ -128,10 +138,10 @@
128 138 $categories,
129 139 array_slice( $other_categories, 1 )
130 140 );
131 141
132 - $set_categories = function ( $categories ) {
133 - $this->categories = $categories;
142 + $set_categories = function ( $categories ) use ( $elements_manager ) {
143 + $elements_manager->categories = $categories;
134 144 };
135 145
136 146 $set_categories->call( $elements_manager, $categories );
137 147 }
@@ -152,10 +162,14 @@
152 162
153 163 /**
154 164 * Init Widgets
155 165 *
156 - * Include widgets files and register them
166 + * Include widget files and register them
157 167 *
168 + * @param object $widgets_manager Widgets Manager.
169 + *
170 + * @return void
171 + *
158 172 * @since 1.0.0
159 173 */
160 174 public function init_widgets( $widgets_manager ) {
161 175
@@ -173,45 +187,24 @@
173 187 continue;
174 188 }
175 189
176 190 if ( ! empty( $element['category'] ) ) {
177 - $widget = false;
178 - $import = apply_filters( 'rtsb_import_status', false );
179 - switch ( $element['category'] ) {
180 - case 'product':
181 - if ( BuilderFns::is_product() || $import ) {
182 - $widget = new $element['base_class']();
183 - }
184 - break;
185 - case 'shop':
186 - if ( BuilderFns::is_archive() || BuilderFns::is_shop() || $import ) {
187 - $widget = new $element['base_class']();
188 - }
189 - break;
190 - case 'archive':
191 - if ( BuilderFns::is_archive() || $import ) {
192 - $widget = new $element['base_class']();
193 - }
194 - break;
195 - case 'cart':
196 - if ( BuilderFns::is_cart() || $import ) {
197 - $widget = new $element['base_class']();
198 - }
199 - break;
200 - case 'checkout':
201 - if ( BuilderFns::is_checkout() || $import ) {
202 - $widget = new $element['base_class']();
203 - }
204 - break;
205 - 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 ) ) {
206 196 $widget = new $element['base_class']();
207 - break;
208 - default:
209 - }
210 - $widget = apply_filters( 'rtsb/element/list/init/widgets', $widget, $element );
197 + }
211 198
212 - if ( $widget && is_object( $widget ) ) {
213 - $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;
214 207 }
215 208 }
216 209 }
217 210 }
@@ -216,8 +209,36 @@
216 209 }
217 210 }
218 211
219 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 + /**
220 241 * Adding custom icons in Elementor
221 242 *
222 243 * @param array $tabs Tabs.
223 244 *
@@ -223,9 +244,9 @@
223 244 *
224 245 * @return array
225 246 */
226 247 public function rtsb_icons( $tabs = [] ) {
227 - $custom_icons = $this->get_icons();
248 + $custom_icons = Fns::get_custom_icon_names();
228 249
229 250 $tabs['shopbuilder-icons'] = [
230 251 'name' => 'rtsb-fonts',
231 252 'label' => esc_html__( 'ShopBuilder Icons', 'shopbuilder' ),
@@ -282,16 +303,34 @@
282 303 *
283 304 * @since 2.0.0
284 305 */
285 306 if ( did_action( 'elementor/loaded' ) && 'elementor' === $this->page_edit_with ) {
286 - 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
287 308 }
288 309
289 310 $parent_class = apply_filters( 'rtsb/builder/content/parent_class', [] );
290 311
291 312 if ( BuilderFns::is_product() ) {
292 - $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 +
293 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 + }
294 333 }
295 334
296 335 if ( BuilderFns::is_shop() ) {
297 336 $parent_class[] = 'shop';
@@ -312,10 +351,34 @@
312 351 if ( BuilderFns::is_checkout() ) {
313 352 if ( ! apply_filters( 'rtsb/multi/step/checkout/widget', true ) ) {
314 353 return;
315 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 + }
316 376 ?>
317 - <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">
318 381 <?php
319 382 }
320 383 }
321 384
@@ -332,9 +395,9 @@
332 395 *
333 396 * @since 2.0.0
334 397 */
335 398 if ( did_action( 'elementor/loaded' ) && 'elementor' === $this->page_edit_with ) {
336 - 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
337 400 }
338 401
339 402 if ( BuilderFns::is_checkout() ) {
340 403 if ( ! apply_filters( 'rtsb/multi/step/checkout/widget', true ) ) {
@@ -342,8 +405,9 @@
342 405 }
343 406 ?>
344 407 </form>
345 408 <?php
409 + do_action( 'woocommerce_after_checkout_form', WC()->checkout() );
346 410 }
347 411 ?>
348 412 </div>
349 413 <!-- After Content end -->
@@ -355,24 +419,28 @@
355 419 *
356 420 * @return void
357 421 */
358 422 public function select2_ajax_posts_filter_autocomplete() {
359 -
423 + if ( ! wp_verify_nonce( Fns::get_nonce(), rtsb()->nonceText ) ) {
424 + wp_send_json_error( [] );
425 + }
360 426 $query_per_page = 15;
361 427 $post_type = 'post';
362 428 $source_name = 'post_type';
363 - $paged = $_POST['page'] ?? 1;
364 -
429 + $paged = absint( $_POST['page'] ?? 1 );
365 430 if ( ! empty( $_POST['post_type'] ) ) {
431 + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
366 432 $post_type = sanitize_text_field( $_POST['post_type'] );
367 433 }
368 434
369 435 if ( ! empty( $_POST['source_name'] ) ) {
436 + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
370 437 $source_name = sanitize_text_field( $_POST['source_name'] );
371 438 }
372 439
440 + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
373 441 $search = ! empty( $_POST['search'] ) ? sanitize_text_field( $_POST['search'] ) : '';
374 - $results = $post_list = [];
442 + $results = $post_list = []; // phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.Found
375 443 switch ( $source_name ) {
376 444 case 'taxonomy':
377 445 $args = [
378 446 'hide_empty' => false,
@@ -377,13 +445,13 @@
377 445 $args = [
378 446 'hide_empty' => false,
379 447 'orderby' => 'name',
380 448 'order' => 'ASC',
381 - 'search' => $search,
449 + 'name__like' => $search,
382 450 'number' => '5',
383 451 ];
384 452
385 - if ( $post_type !== 'all' ) {
453 + if ( 'all' !== $post_type ) {
386 454 $args['taxonomy'] = $post_type;
387 455 }
388 456
389 457 $post_list = wp_list_pluck( get_terms( $args ), 'name', 'term_id' );
@@ -430,17 +498,22 @@
430 498 * @return void
431 499 */
432 500 public function select2_ajax_get_posts_value_titles() {
433 501
502 + if ( ! wp_verify_nonce( Fns::get_nonce(), rtsb()->nonceText ) ) {
503 + wp_send_json_error( [] );
504 + }
505 +
434 506 if ( empty( $_POST['id'] ) ) {
435 507 wp_send_json_error( [] );
436 508 }
437 509
510 + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
438 511 if ( empty( array_filter( $_POST['id'] ) ) ) {
439 512 wp_send_json_error( [] );
440 513 }
441 514 $ids = array_map( 'intval', $_POST['id'] );
442 - $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
443 516
444 517 switch ( $source_name ) {
445 518 case 'taxonomy':
446 519 $args = [
@@ -449,9 +522,11 @@
449 522 'order' => 'ASC',
450 523 'include' => implode( ',', $ids ),
451 524 ];
452 525
453 - 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
454 529 $args['taxonomy'] = sanitize_text_field( $_POST['post_type'] );
455 530 }
456 531
457 532 $response = wp_list_pluck( get_terms( $args ), 'name', 'term_id' );
@@ -469,9 +544,9 @@
469 544 break;
470 545 default:
471 546 $post_info = get_posts(
472 547 [
473 - '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
474 549 'include' => implode( ',', $ids ),
475 550 ]
476 551 );
477 552 $response = wp_list_pluck( $post_info, 'post_title', 'ID' );
@@ -487,12 +562,12 @@
487 562
488 563 /**
489 564 * Ajax callback for rt-select2
490 565 *
491 - * @param $post_type
492 - * @param $limit
493 - * @param $search
494 - * @param $paged
566 + * @param string $post_type Post type.
567 + * @param int $limit Limit.
568 + * @param string $search Search.
569 + * @param int $paged Paged.
495 570 *
496 571 * @return array
497 572 */
498 573 public function get_query_data( $post_type = 'any', $limit = 10, $search = '', $paged = 1 ) {
@@ -529,10 +604,19 @@
529 604 if ( ! empty( $search ) ) {
530 605 $where .= $wpdb->prepare( " AND {$wpdb->posts}.post_title LIKE %s", '%' . esc_sql( $search ) . '%' );
531 606 }
532 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 +
533 617 $query = "select post_title,ID from $wpdb->posts where post_status = 'publish' {$where} {$limit}";
534 - $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
535 619
536 620 if ( ! empty( $results ) ) {
537 621 foreach ( $results as $row ) {
538 622 $data[ $row->ID ] = $row->post_title . ' [#' . $row->ID . ']';
@@ -563,10 +647,10 @@
563 647
564 648 $pro_widgets = [
565 649 [
566 650 'name' => 'rtsb-ajax-product-filters',
567 - 'title' => esc_html__( 'Ajax Product Filters', 'testimonial-slider-showcase' ),
568 - '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' ),
569 653 'icon' => 'rtsb-el-custom rtsb-element icon-rtsb-ajax-product-filters rtsb-promotional-element',
570 654 'categories' => '[ "rtsb-shopbuilder" ]',
571 655 ],
572 656 ];