PluginProbe
ShopBuilder – WooCommerce Builder For Elementor / 2.1.3
ShopBuilder – WooCommerce Builder For Elementor v2.1.3
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 +90 -187 3.4.22.1.3 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;
13 12 use RadiusTheme\SB\Traits\SingletonTrait;
14 13 use RadiusTheme\SB\Controllers\Builder\BuilderCpt;
15 14 use RadiusTheme\SB\Controllers\Hooks\BuilderHooks;
16 15 use RadiusTheme\SB\Elementor\Controls\ImageSelectorControl;
17 16 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,24 +54,28 @@
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 +
58 59 if ( ! is_admin() ) {
59 60 BuilderHooks::instance();
60 61 }
61 62
62 63 BuilderCpt::instance();
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 - }
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 +
68 72 // RT Select2 Ajax.
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' ] );
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' ] );
71 75 // Select2 ajax save data.
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' ] );
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' ] );
74 78 }
75 79
76 80 /**
77 81 * Apply for frontend.
@@ -88,20 +92,13 @@
88 92 *
89 93 * @return void
90 94 */
91 95 public function elementor_init() {
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 - );
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' ] );
104 101 }
105 102
106 103 /**
107 104 * Register Category
@@ -110,29 +107,21 @@
110 107 *
111 108 * @return void
112 109 */
113 110 public function add_elementor_widget_categories( $elements_manager ) {
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 );
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 + ];
118 122
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 -
123 + $categories = apply_filters( 'rtsb_elementor_widgets_category_lists', $categories );
135 124 $other_categories = $elements_manager->get_categories();
136 125 $categories = array_merge(
137 126 array_slice( $other_categories, 0, 1 ),
138 127 $categories,
@@ -138,10 +127,10 @@
138 127 $categories,
139 128 array_slice( $other_categories, 1 )
140 129 );
141 130
142 - $set_categories = function ( $categories ) use ( $elements_manager ) {
143 - $elements_manager->categories = $categories;
131 + $set_categories = function ( $categories ) {
132 + $this->categories = $categories;
144 133 };
145 134
146 135 $set_categories->call( $elements_manager, $categories );
147 136 }
@@ -162,14 +151,10 @@
162 151
163 152 /**
164 153 * Init Widgets
165 154 *
166 - * Include widget files and register them
155 + * Include widgets files and register them
167 156 *
168 - * @param object $widgets_manager Widgets Manager.
169 - *
170 - * @return void
171 - *
172 157 * @since 1.0.0
173 158 */
174 159 public function init_widgets( $widgets_manager ) {
175 160
@@ -187,24 +172,45 @@
187 172 continue;
188 173 }
189 174
190 175 if ( ! empty( $element['category'] ) ) {
191 - try {
192 - $widget = false;
193 - $import = apply_filters( 'rtsb_import_status', false );
194 -
195 - if ( $this->should_register_widget( $element['category'], $import ) ) {
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':
196 205 $widget = new $element['base_class']();
197 - }
206 + break;
207 + default:
208 + }
209 + $widget = apply_filters( 'rtsb/element/list/init/widgets', $widget, $element );
198 210
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;
211 + if ( $widget && is_object( $widget ) ) {
212 + $widgets_manager->register( $widget );
207 213 }
208 214 }
209 215 }
210 216 }
@@ -209,48 +215,8 @@
209 215 }
210 216 }
211 217
212 218 /**
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 - // On the frontend the Elementor Widgets Manager can initialise at an
223 - // unpredictable time — e.g. Elementor Pro's Theme Builder boots it on
224 - // template_redirect — when WooCommerce query conditionals such as
225 - // is_shop()/is_archive() are not yet reliable. Widget registration is
226 - // cached for the whole request, so gating it by those conditionals can
227 - // permanently skip ShopBuilder widgets, leaving the builder template to
228 - // render empty container shells. Register unconditionally on the
229 - // frontend; keep the per-category gate only for the editor widget panel.
230 - if ( ! is_admin() ) {
231 - return true;
232 - }
233 -
234 - switch ( $category ) {
235 - case 'product':
236 - return BuilderFns::is_product() || $import;
237 - case 'shop':
238 - return BuilderFns::is_archive() || BuilderFns::is_shop() || $import;
239 - case 'archive':
240 - return BuilderFns::is_archive() || $import;
241 - case 'cart':
242 - return BuilderFns::is_cart() || $import;
243 - case 'checkout':
244 - return BuilderFns::is_checkout() || $import;
245 - case 'general':
246 - return true;
247 - default:
248 - return false;
249 - }
250 - }
251 -
252 - /**
253 219 * Adding custom icons in Elementor
254 220 *
255 221 * @param array $tabs Tabs.
256 222 *
@@ -256,9 +222,9 @@
256 222 *
257 223 * @return array
258 224 */
259 225 public function rtsb_icons( $tabs = [] ) {
260 - $custom_icons = Fns::get_custom_icon_names();
226 + $custom_icons = $this->get_icons();
261 227
262 228 $tabs['shopbuilder-icons'] = [
263 229 'name' => 'rtsb-fonts',
264 230 'label' => esc_html__( 'ShopBuilder Icons', 'shopbuilder' ),
@@ -315,34 +281,16 @@
315 281 *
316 282 * @since 2.0.0
317 283 */
318 284 if ( did_action( 'elementor/loaded' ) && 'elementor' === $this->page_edit_with ) {
319 - do_action( 'elementor/page_templates/header-footer/before_content' ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
285 + do_action( 'elementor/page_templates/header-footer/before_content' );
320 286 }
321 287
322 288 $parent_class = apply_filters( 'rtsb/builder/content/parent_class', [] );
323 289
324 290 if ( BuilderFns::is_product() ) {
325 - $product_id = Fns::get_prepared_product_id();
326 -
327 - // Ensure the WordPress global $product is a valid WC_Product before
328 - // wc_get_product_class() fires the `woocommerce_post_class` filter.
329 - // Some themes (e.g. Blocksy) read the global $product directly inside
330 - // that filter and call methods like get_type() on it, which throws a
331 - // fatal error when the global is still an unresolved string.
332 - $_product = Fns::get_product();
333 -
291 + $product_id = Fns::get_prepared_product_id();
334 292 $parent_class = wc_get_product_class( $parent_class, $product_id );
335 -
336 - if ( function_exists( 'rtwpvs' ) && $_product instanceof \WC_Product ) {
337 - if ( $_product->is_type( 'variable' ) ) {
338 - $parent_class[] = 'rtwpvs-product';
339 - $beside_label = function_exists( 'rtwpvsp' ) && rtwpvs()->get_option( 'attribute_on_click_behavior' );
340 - if ( $beside_label ) {
341 - $parent_class[] = 'rtwpvs-selected-term-beside-label';
342 - }
343 - }
344 - }
345 293 }
346 294
347 295 if ( BuilderFns::is_shop() ) {
348 296 $parent_class[] = 'shop';
@@ -363,34 +311,10 @@
363 311 if ( BuilderFns::is_checkout() ) {
364 312 if ( ! apply_filters( 'rtsb/multi/step/checkout/widget', true ) ) {
365 313 return;
366 314 }
367 -
368 - $checkout = WC()->checkout();
369 -
370 - // Temporarily remove callbacks that Elementor checkout
371 - // handles via separate widgets to prevent duplicates.
372 - remove_action( 'woocommerce_before_checkout_form', [ Hooks\ActionHooks::class, 'woocommerce_checkout_login_form' ], 10 );
373 - remove_action( 'woocommerce_before_checkout_form', [ Hooks\ActionHooks::class, 'woocommerce_checkout_coupon_form' ], 10 );
374 - remove_action( 'woocommerce_before_checkout_form', [ Fns::class, 'woocommerce_output_all_notices' ], 10 );
375 -
376 - do_action( 'woocommerce_before_checkout_form', $checkout );
377 -
378 - // Restore callbacks for non-Elementor contexts.
379 - add_action( 'woocommerce_before_checkout_form', [ Hooks\ActionHooks::class, 'woocommerce_checkout_login_form' ], 10 );
380 - add_action( 'woocommerce_before_checkout_form', [ Hooks\ActionHooks::class, 'woocommerce_checkout_coupon_form' ], 10 );
381 - add_action( 'woocommerce_before_checkout_form', [ Fns::class, 'woocommerce_output_all_notices' ], 10 );
382 -
383 - // If checkout registration is disabled and not logged in, the user cannot checkout.
384 - if ( ! $checkout->is_registration_enabled() && $checkout->is_registration_required() && ! is_user_logged_in() ) {
385 - echo esc_html( apply_filters( 'woocommerce_checkout_must_be_logged_in_message', __( 'You must be logged in to checkout.', 'shopbuilder' ) ) );
386 - return;
387 - }
388 315 ?>
389 - <!--
390 - woocommerce-checkout added for fix issue with payment method ( Revolut Pay )
391 - -->
392 - <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">
316 + <form name="checkout" method="post" class="rtsb-woocommerce-checkout checkout" action="<?php echo esc_url( wc_get_checkout_url() ); ?>" enctype="multipart/form-data">
393 317 <?php
394 318 }
395 319 }
396 320
@@ -407,9 +331,9 @@
407 331 *
408 332 * @since 2.0.0
409 333 */
410 334 if ( did_action( 'elementor/loaded' ) && 'elementor' === $this->page_edit_with ) {
411 - do_action( 'elementor/page_templates/header-footer/after_content' ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
335 + do_action( 'elementor/page_templates/header-footer/after_content' );
412 336 }
413 337
414 338 if ( BuilderFns::is_checkout() ) {
415 339 if ( ! apply_filters( 'rtsb/multi/step/checkout/widget', true ) ) {
@@ -417,9 +341,8 @@
417 341 }
418 342 ?>
419 343 </form>
420 344 <?php
421 - do_action( 'woocommerce_after_checkout_form', WC()->checkout() );
422 345 }
423 346 ?>
424 347 </div>
425 348 <!-- After Content end -->
@@ -431,28 +354,24 @@
431 354 *
432 355 * @return void
433 356 */
434 357 public function select2_ajax_posts_filter_autocomplete() {
435 - if ( ! wp_verify_nonce( Fns::get_nonce(), rtsb()->nonceText ) ) {
436 - wp_send_json_error( [] );
437 - }
358 +
438 359 $query_per_page = 15;
439 360 $post_type = 'post';
440 361 $source_name = 'post_type';
441 - $paged = absint( $_POST['page'] ?? 1 );
362 + $paged = $_POST['page'] ?? 1;
363 +
442 364 if ( ! empty( $_POST['post_type'] ) ) {
443 - // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
444 365 $post_type = sanitize_text_field( $_POST['post_type'] );
445 366 }
446 367
447 368 if ( ! empty( $_POST['source_name'] ) ) {
448 - // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
449 369 $source_name = sanitize_text_field( $_POST['source_name'] );
450 370 }
451 371
452 - // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
453 372 $search = ! empty( $_POST['search'] ) ? sanitize_text_field( $_POST['search'] ) : '';
454 - $results = $post_list = []; // phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.Found
373 + $results = $post_list = [];
455 374 switch ( $source_name ) {
456 375 case 'taxonomy':
457 376 $args = [
458 377 'hide_empty' => false,
@@ -457,13 +376,13 @@
457 376 $args = [
458 377 'hide_empty' => false,
459 378 'orderby' => 'name',
460 379 'order' => 'ASC',
461 - 'name__like' => $search,
380 + 'search' => $search,
462 381 'number' => '5',
463 382 ];
464 383
465 - if ( 'all' !== $post_type ) {
384 + if ( $post_type !== 'all' ) {
466 385 $args['taxonomy'] = $post_type;
467 386 }
468 387
469 388 $post_list = wp_list_pluck( get_terms( $args ), 'name', 'term_id' );
@@ -510,22 +429,17 @@
510 429 * @return void
511 430 */
512 431 public function select2_ajax_get_posts_value_titles() {
513 432
514 - if ( ! wp_verify_nonce( Fns::get_nonce(), rtsb()->nonceText ) ) {
515 - wp_send_json_error( [] );
516 - }
517 -
518 433 if ( empty( $_POST['id'] ) ) {
519 434 wp_send_json_error( [] );
520 435 }
521 436
522 - // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
523 437 if ( empty( array_filter( $_POST['id'] ) ) ) {
524 438 wp_send_json_error( [] );
525 439 }
526 440 $ids = array_map( 'intval', $_POST['id'] );
527 - $source_name = ! empty( $_POST['source_name'] ) ? sanitize_text_field( $_POST['source_name'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
441 + $source_name = ! empty( $_POST['source_name'] ) ? sanitize_text_field( $_POST['source_name'] ) : '';
528 442
529 443 switch ( $source_name ) {
530 444 case 'taxonomy':
531 445 $args = [
@@ -534,11 +448,9 @@
534 448 'order' => 'ASC',
535 449 'include' => implode( ',', $ids ),
536 450 ];
537 451
538 - // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated
539 - if ( 'all' !== $_POST['post_type'] ) {
540 - // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
452 + if ( $_POST['post_type'] !== 'all' ) {
541 453 $args['taxonomy'] = sanitize_text_field( $_POST['post_type'] );
542 454 }
543 455
544 456 $response = wp_list_pluck( get_terms( $args ), 'name', 'term_id' );
@@ -556,9 +468,9 @@
556 468 break;
557 469 default:
558 470 $post_info = get_posts(
559 471 [
560 - 'post_type' => sanitize_text_field( $_POST['post_type'] ), // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotValidated
472 + 'post_type' => sanitize_text_field( $_POST['post_type'] ),
561 473 'include' => implode( ',', $ids ),
562 474 ]
563 475 );
564 476 $response = wp_list_pluck( $post_info, 'post_title', 'ID' );
@@ -574,12 +486,12 @@
574 486
575 487 /**
576 488 * Ajax callback for rt-select2
577 489 *
578 - * @param string $post_type Post type.
579 - * @param int $limit Limit.
580 - * @param string $search Search.
581 - * @param int $paged Paged.
490 + * @param $post_type
491 + * @param $limit
492 + * @param $search
493 + * @param $paged
582 494 *
583 495 * @return array
584 496 */
585 497 public function get_query_data( $post_type = 'any', $limit = 10, $search = '', $paged = 1 ) {
@@ -616,19 +528,10 @@
616 528 if ( ! empty( $search ) ) {
617 529 $where .= $wpdb->prepare( " AND {$wpdb->posts}.post_title LIKE %s", '%' . esc_sql( $search ) . '%' );
618 530 }
619 531
620 - // Add a query for shop_coupon post-type to check 'show on frontend' meta.
621 - if ( 'shop_coupon' === $post_type ) {
622 - $where .= " AND {$wpdb->posts}.ID IN (
623 - SELECT post_id FROM {$wpdb->postmeta}
624 - WHERE meta_key = 'rtsb_show_on_frontend'
625 - AND meta_value = 'on'
626 - )";
627 - }
628 -
629 532 $query = "select post_title,ID from $wpdb->posts where post_status = 'publish' {$where} {$limit}";
630 - $results = $wpdb->get_results( $query ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching
533 + $results = $wpdb->get_results( $query );
631 534
632 535 if ( ! empty( $results ) ) {
633 536 foreach ( $results as $row ) {
634 537 $data[ $row->ID ] = $row->post_title . ' [#' . $row->ID . ']';
@@ -659,10 +562,10 @@
659 562
660 563 $pro_widgets = [
661 564 [
662 565 'name' => 'rtsb-ajax-product-filters',
663 - 'title' => esc_html__( 'Ajax Product Filters', 'shopbuilder' ),
664 - 'description' => esc_html__( 'Ajax Product Filters', 'shopbuilder' ),
566 + 'title' => esc_html__( 'Ajax Product Filters', 'testimonial-slider-showcase' ),
567 + 'description' => esc_html__( 'Ajax Product Filters', 'testimonial-slider-showcase' ),
665 568 'icon' => 'rtsb-el-custom rtsb-element icon-rtsb-ajax-product-filters rtsb-promotional-element',
666 569 'categories' => '[ "rtsb-shopbuilder" ]',
667 570 ],
668 571 ];