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 +138 -76 2.1.103.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
@@ -112,11 +115,11 @@
112 115 $builder_id = get_queried_object_id();
113 116 $id = Fns::wpml_object_id( $builder_id, BuilderFns::$post_type_tb, 'default' );
114 117 $type = BuilderFns::builder_type( $id );
115 118
116 - if ( $type ) {
119 + if ( $type && 'popup-builder' !== $type ) {
117 120 $builder_type = ! empty( BuilderFns::builder_page_types()[ $type ] ) ? BuilderFns::builder_page_types()[ $type ] : '';
118 - $builder_type = str_replace( 'My Account - ', '', $builder_type );
121 + $builder_type = str_replace( [ 'My Account - ', 'Endpoint: ' ], '', $builder_type );
119 122 $categories['rtsb-shopbuilder'] = [
120 123 'title' => esc_html__( 'ShopBuilder', 'shopbuilder' ) . ' - ' . ucfirst( $builder_type ),
121 124 'icon' => 'fa fa-plug',
122 125 ];
@@ -135,10 +138,10 @@
135 138 $categories,
136 139 array_slice( $other_categories, 1 )
137 140 );
138 141
139 - $set_categories = function ( $categories ) {
140 - $this->categories = $categories;
142 + $set_categories = function ( $categories ) use ( $elements_manager ) {
143 + $elements_manager->categories = $categories;
141 144 };
142 145
143 146 $set_categories->call( $elements_manager, $categories );
144 147 }
@@ -159,10 +162,14 @@
159 162
160 163 /**
161 164 * Init Widgets
162 165 *
163 - * Include widgets files and register them
166 + * Include widget files and register them
164 167 *
168 + * @param object $widgets_manager Widgets Manager.
169 + *
170 + * @return void
171 + *
165 172 * @since 1.0.0
166 173 */
167 174 public function init_widgets( $widgets_manager ) {
168 175
@@ -180,45 +187,24 @@
180 187 continue;
181 188 }
182 189
183 190 if ( ! empty( $element['category'] ) ) {
184 - $widget = false;
185 - $import = apply_filters( 'rtsb_import_status', false );
186 - switch ( $element['category'] ) {
187 - case 'product':
188 - if ( BuilderFns::is_product() || $import ) {
189 - $widget = new $element['base_class']();
190 - }
191 - break;
192 - case 'shop':
193 - if ( BuilderFns::is_archive() || BuilderFns::is_shop() || $import ) {
194 - $widget = new $element['base_class']();
195 - }
196 - break;
197 - case 'archive':
198 - if ( BuilderFns::is_archive() || $import ) {
199 - $widget = new $element['base_class']();
200 - }
201 - break;
202 - case 'cart':
203 - if ( BuilderFns::is_cart() || $import ) {
204 - $widget = new $element['base_class']();
205 - }
206 - break;
207 - case 'checkout':
208 - if ( BuilderFns::is_checkout() || $import ) {
209 - $widget = new $element['base_class']();
210 - }
211 - break;
212 - 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 ) ) {
213 196 $widget = new $element['base_class']();
214 - break;
215 - default:
216 - }
217 - $widget = apply_filters( 'rtsb/element/list/init/widgets', $widget, $element );
197 + }
218 198
219 - if ( $widget && is_object( $widget ) ) {
220 - $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;
221 207 }
222 208 }
223 209 }
224 210 }
@@ -223,8 +209,36 @@
223 209 }
224 210 }
225 211
226 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 + /**
227 241 * Adding custom icons in Elementor
228 242 *
229 243 * @param array $tabs Tabs.
230 244 *
@@ -230,9 +244,9 @@
230 244 *
231 245 * @return array
232 246 */
233 247 public function rtsb_icons( $tabs = [] ) {
234 - $custom_icons = $this->get_icons();
248 + $custom_icons = Fns::get_custom_icon_names();
235 249
236 250 $tabs['shopbuilder-icons'] = [
237 251 'name' => 'rtsb-fonts',
238 252 'label' => esc_html__( 'ShopBuilder Icons', 'shopbuilder' ),
@@ -289,18 +303,26 @@
289 303 *
290 304 * @since 2.0.0
291 305 */
292 306 if ( did_action( 'elementor/loaded' ) && 'elementor' === $this->page_edit_with ) {
293 - 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
294 308 }
295 309
296 310 $parent_class = apply_filters( 'rtsb/builder/content/parent_class', [] );
297 311
298 312 if ( BuilderFns::is_product() ) {
299 - $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 +
300 322 $parent_class = wc_get_product_class( $parent_class, $product_id );
301 - if ( function_exists( 'rtwpvs' ) ) {
302 - $_product = Fns::get_product();
323 +
324 + if ( function_exists( 'rtwpvs' ) && $_product instanceof \WC_Product ) {
303 325 if ( $_product->is_type( 'variable' ) ) {
304 326 $parent_class[] = 'rtwpvs-product';
305 327 $beside_label = function_exists( 'rtwpvsp' ) && rtwpvs()->get_option( 'attribute_on_click_behavior' );
306 328 if ( $beside_label ) {
@@ -329,10 +351,34 @@
329 351 if ( BuilderFns::is_checkout() ) {
330 352 if ( ! apply_filters( 'rtsb/multi/step/checkout/widget', true ) ) {
331 353 return;
332 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 + }
333 376 ?>
334 - <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">
335 381 <?php
336 382 }
337 383 }
338 384
@@ -349,9 +395,9 @@
349 395 *
350 396 * @since 2.0.0
351 397 */
352 398 if ( did_action( 'elementor/loaded' ) && 'elementor' === $this->page_edit_with ) {
353 - 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
354 400 }
355 401
356 402 if ( BuilderFns::is_checkout() ) {
357 403 if ( ! apply_filters( 'rtsb/multi/step/checkout/widget', true ) ) {
@@ -359,8 +405,9 @@
359 405 }
360 406 ?>
361 407 </form>
362 408 <?php
409 + do_action( 'woocommerce_after_checkout_form', WC()->checkout() );
363 410 }
364 411 ?>
365 412 </div>
366 413 <!-- After Content end -->
@@ -380,17 +427,20 @@
380 427 $post_type = 'post';
381 428 $source_name = 'post_type';
382 429 $paged = absint( $_POST['page'] ?? 1 );
383 430 if ( ! empty( $_POST['post_type'] ) ) {
431 + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
384 432 $post_type = sanitize_text_field( $_POST['post_type'] );
385 433 }
386 434
387 435 if ( ! empty( $_POST['source_name'] ) ) {
436 + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
388 437 $source_name = sanitize_text_field( $_POST['source_name'] );
389 438 }
390 439
440 + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
391 441 $search = ! empty( $_POST['search'] ) ? sanitize_text_field( $_POST['search'] ) : '';
392 - $results = $post_list = [];
442 + $results = $post_list = []; // phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.Found
393 443 switch ( $source_name ) {
394 444 case 'taxonomy':
395 445 $args = [
396 446 'hide_empty' => false,
@@ -395,13 +445,13 @@
395 445 $args = [
396 446 'hide_empty' => false,
397 447 'orderby' => 'name',
398 448 'order' => 'ASC',
399 - 'search' => $search,
449 + 'name__like' => $search,
400 450 'number' => '5',
401 451 ];
402 452
403 - if ( $post_type !== 'all' ) {
453 + if ( 'all' !== $post_type ) {
404 454 $args['taxonomy'] = $post_type;
405 455 }
406 456
407 457 $post_list = wp_list_pluck( get_terms( $args ), 'name', 'term_id' );
@@ -456,13 +506,14 @@
456 506 if ( empty( $_POST['id'] ) ) {
457 507 wp_send_json_error( [] );
458 508 }
459 509
510 + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
460 511 if ( empty( array_filter( $_POST['id'] ) ) ) {
461 512 wp_send_json_error( [] );
462 513 }
463 514 $ids = array_map( 'intval', $_POST['id'] );
464 - $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
465 516
466 517 switch ( $source_name ) {
467 518 case 'taxonomy':
468 519 $args = [
@@ -471,9 +522,11 @@
471 522 'order' => 'ASC',
472 523 'include' => implode( ',', $ids ),
473 524 ];
474 525
475 - 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
476 529 $args['taxonomy'] = sanitize_text_field( $_POST['post_type'] );
477 530 }
478 531
479 532 $response = wp_list_pluck( get_terms( $args ), 'name', 'term_id' );
@@ -491,9 +544,9 @@
491 544 break;
492 545 default:
493 546 $post_info = get_posts(
494 547 [
495 - '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
496 549 'include' => implode( ',', $ids ),
497 550 ]
498 551 );
499 552 $response = wp_list_pluck( $post_info, 'post_title', 'ID' );
@@ -509,12 +562,12 @@
509 562
510 563 /**
511 564 * Ajax callback for rt-select2
512 565 *
513 - * @param $post_type
514 - * @param $limit
515 - * @param $search
516 - * @param $paged
566 + * @param string $post_type Post type.
567 + * @param int $limit Limit.
568 + * @param string $search Search.
569 + * @param int $paged Paged.
517 570 *
518 571 * @return array
519 572 */
520 573 public function get_query_data( $post_type = 'any', $limit = 10, $search = '', $paged = 1 ) {
@@ -549,8 +602,17 @@
549 602 }
550 603
551 604 if ( ! empty( $search ) ) {
552 605 $where .= $wpdb->prepare( " AND {$wpdb->posts}.post_title LIKE %s", '%' . esc_sql( $search ) . '%' );
606 + }
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 + )";
553 615 }
554 616
555 617 $query = "select post_title,ID from $wpdb->posts where post_status = 'publish' {$where} {$limit}";
556 618 $results = $wpdb->get_results( $query ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching