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 -90 2.1.33.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';
@@ -311,10 +351,34 @@
311 351 if ( BuilderFns::is_checkout() ) {
312 352 if ( ! apply_filters( 'rtsb/multi/step/checkout/widget', true ) ) {
313 353 return;
314 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 + }
315 376 ?>
316 - <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">
317 381 <?php
318 382 }
319 383 }
320 384
@@ -331,9 +395,9 @@
331 395 *
332 396 * @since 2.0.0
333 397 */
334 398 if ( did_action( 'elementor/loaded' ) && 'elementor' === $this->page_edit_with ) {
335 - 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
336 400 }
337 401
338 402 if ( BuilderFns::is_checkout() ) {
339 403 if ( ! apply_filters( 'rtsb/multi/step/checkout/widget', true ) ) {
@@ -341,8 +405,9 @@
341 405 }
342 406 ?>
343 407 </form>
344 408 <?php
409 + do_action( 'woocommerce_after_checkout_form', WC()->checkout() );
345 410 }
346 411 ?>
347 412 </div>
348 413 <!-- After Content end -->
@@ -354,24 +419,28 @@
354 419 *
355 420 * @return void
356 421 */
357 422 public function select2_ajax_posts_filter_autocomplete() {
358 -
423 + if ( ! wp_verify_nonce( Fns::get_nonce(), rtsb()->nonceText ) ) {
424 + wp_send_json_error( [] );
425 + }
359 426 $query_per_page = 15;
360 427 $post_type = 'post';
361 428 $source_name = 'post_type';
362 - $paged = $_POST['page'] ?? 1;
363 -
429 + $paged = absint( $_POST['page'] ?? 1 );
364 430 if ( ! empty( $_POST['post_type'] ) ) {
431 + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
365 432 $post_type = sanitize_text_field( $_POST['post_type'] );
366 433 }
367 434
368 435 if ( ! empty( $_POST['source_name'] ) ) {
436 + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
369 437 $source_name = sanitize_text_field( $_POST['source_name'] );
370 438 }
371 439
440 + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
372 441 $search = ! empty( $_POST['search'] ) ? sanitize_text_field( $_POST['search'] ) : '';
373 - $results = $post_list = [];
442 + $results = $post_list = []; // phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.Found
374 443 switch ( $source_name ) {
375 444 case 'taxonomy':
376 445 $args = [
377 446 'hide_empty' => false,
@@ -376,13 +445,13 @@
376 445 $args = [
377 446 'hide_empty' => false,
378 447 'orderby' => 'name',
379 448 'order' => 'ASC',
380 - 'search' => $search,
449 + 'name__like' => $search,
381 450 'number' => '5',
382 451 ];
383 452
384 - if ( $post_type !== 'all' ) {
453 + if ( 'all' !== $post_type ) {
385 454 $args['taxonomy'] = $post_type;
386 455 }
387 456
388 457 $post_list = wp_list_pluck( get_terms( $args ), 'name', 'term_id' );
@@ -429,17 +498,22 @@
429 498 * @return void
430 499 */
431 500 public function select2_ajax_get_posts_value_titles() {
432 501
502 + if ( ! wp_verify_nonce( Fns::get_nonce(), rtsb()->nonceText ) ) {
503 + wp_send_json_error( [] );
504 + }
505 +
433 506 if ( empty( $_POST['id'] ) ) {
434 507 wp_send_json_error( [] );
435 508 }
436 509
510 + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
437 511 if ( empty( array_filter( $_POST['id'] ) ) ) {
438 512 wp_send_json_error( [] );
439 513 }
440 514 $ids = array_map( 'intval', $_POST['id'] );
441 - $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
442 516
443 517 switch ( $source_name ) {
444 518 case 'taxonomy':
445 519 $args = [
@@ -448,9 +522,11 @@
448 522 'order' => 'ASC',
449 523 'include' => implode( ',', $ids ),
450 524 ];
451 525
452 - 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
453 529 $args['taxonomy'] = sanitize_text_field( $_POST['post_type'] );
454 530 }
455 531
456 532 $response = wp_list_pluck( get_terms( $args ), 'name', 'term_id' );
@@ -468,9 +544,9 @@
468 544 break;
469 545 default:
470 546 $post_info = get_posts(
471 547 [
472 - '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
473 549 'include' => implode( ',', $ids ),
474 550 ]
475 551 );
476 552 $response = wp_list_pluck( $post_info, 'post_title', 'ID' );
@@ -486,12 +562,12 @@
486 562
487 563 /**
488 564 * Ajax callback for rt-select2
489 565 *
490 - * @param $post_type
491 - * @param $limit
492 - * @param $search
493 - * @param $paged
566 + * @param string $post_type Post type.
567 + * @param int $limit Limit.
568 + * @param string $search Search.
569 + * @param int $paged Paged.
494 570 *
495 571 * @return array
496 572 */
497 573 public function get_query_data( $post_type = 'any', $limit = 10, $search = '', $paged = 1 ) {
@@ -528,10 +604,19 @@
528 604 if ( ! empty( $search ) ) {
529 605 $where .= $wpdb->prepare( " AND {$wpdb->posts}.post_title LIKE %s", '%' . esc_sql( $search ) . '%' );
530 606 }
531 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 +
532 617 $query = "select post_title,ID from $wpdb->posts where post_status = 'publish' {$where} {$limit}";
533 - $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
534 619
535 620 if ( ! empty( $results ) ) {
536 621 foreach ( $results as $row ) {
537 622 $data[ $row->ID ] = $row->post_title . ' [#' . $row->ID . ']';
@@ -562,10 +647,10 @@
562 647
563 648 $pro_widgets = [
564 649 [
565 650 'name' => 'rtsb-ajax-product-filters',
566 - 'title' => esc_html__( 'Ajax Product Filters', 'testimonial-slider-showcase' ),
567 - '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' ),
568 653 'icon' => 'rtsb-el-custom rtsb-element icon-rtsb-ajax-product-filters rtsb-promotional-element',
569 654 'categories' => '[ "rtsb-shopbuilder" ]',
570 655 ],
571 656 ];