PluginProbe
ShopBuilder – WooCommerce Builder For Elementor / 3.1.5
ShopBuilder – WooCommerce Builder For Elementor v3.1.5
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
shopbuilder / app / Controllers / AssetRegistry.php

AssetRegistry.php in ShopBuilder – WooCommerce Builder For Elementor 3.1.5, at app/Controllers/AssetRegistry.php

1,338 lines 31.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Class AssetRegistry
4 *
5 * Gathers and processes frontend JS and CSS assets based on active ShopBuilder modules.
6 *
7 * @package RadiusTheme\SB\Services
8 * @since 1.0.0
9 */
10
11 namespace RadiusTheme\SB\Controllers;
12
13 use RadiusTheme\SB\Helpers\Fns;
14 use RadiusTheme\SB\Helpers\BuilderFns;
15 use RadiusTheme\SB\Models\AssetBundler;
16 use RadiusTheme\SB\Traits\SingletonTrait;
17
18 if ( ! defined( 'ABSPATH' ) ) {
19 exit;
20 }
21
22 /**
23 * Class AssetRegistry
24 */
25 class AssetRegistry {
26 use SingletonTrait;
27
28 /**
29 * List of enabled module slugs.
30 *
31 * @var array
32 */
33 protected $enabled_modules = [];
34
35 /**
36 * List of enabled modules JS.
37 *
38 * @var array
39 */
40 protected $enabled_modules_js = [];
41
42 /**
43 * List of enabled modules CSS.
44 *
45 * @var array
46 */
47 protected $enabled_modules_css = [];
48
49 /**
50 * List of enabled Elementor widgets.
51 *
52 * @var array
53 */
54 protected $enabled_el_widgets = [];
55
56 /**
57 * List of enabled Elementor widgets.
58 *
59 * @var array
60 */
61 protected $enabled_widgets = [];
62
63 /**
64 * Full paths to JS assets.
65 *
66 * @var array
67 */
68 protected $js_files = [];
69
70 /**
71 * Full paths to CSS assets.
72 *
73 * @var array
74 */
75 protected $css_files = [];
76
77 /**
78 * Cached bundled JS asset.
79 *
80 * @var array|null
81 */
82 protected $bundled_js = null;
83
84 /**
85 * Cached bundled CSS asset.
86 *
87 * @var array|null
88 */
89 protected $bundled_css = null;
90
91 /**
92 * RTL suffix.
93 *
94 * @var string
95 */
96 private $suffix = '';
97
98 /**
99 * Directory path.
100 *
101 * @var string
102 */
103 private $dir;
104
105 /**
106 * Cached bundled assets.
107 *
108 * @var array|null
109 */
110 private $bundled_assets = null;
111
112 /**
113 * List of loaded asset types.
114 *
115 * @var array
116 */
117 private static $loaded_types = [];
118
119 /**
120 * List of loaded shared assets.
121 *
122 * @var array
123 */
124 private $shared_loaded = [];
125
126 /**
127 * Contextual loading.
128 *
129 * @var bool
130 */
131 private $contextual_loading;
132
133 /**
134 * Class constructor.
135 */
136 private function __construct() {
137 $this->enabled_modules = $this->get_enabled_modules();
138 $this->enabled_modules_js = $this->get_enabled_modules_js_with_context();
139 $this->enabled_modules_css = $this->get_enabled_modules_css_with_context();
140 $this->enabled_el_widgets = $this->get_enabled_el_widgets();
141 $this->enabled_widgets = $this->get_enabled_widgets_with_context();
142 $this->suffix = is_rtl() ? '-rtl' : '';
143 $this->dir = is_rtl() ? trailingslashit( 'rtl' ) : trailingslashit( 'css' );
144 $this->contextual_loading = Fns::is_contextual_loading();
145 }
146
147 /**
148 * Get bundled JS assets by context.
149 *
150 * @return array
151 */
152 public function get_bundled_js_by_context() {
153 $js_files_by_context = $this->get_contextual_assets( 'js' );
154 $bundled_contexts = [];
155
156 foreach ( $js_files_by_context as $context => $files ) {
157 if ( empty( $files ) ) {
158 continue;
159 }
160
161 $bundler = new AssetBundler( "rtsb-bundled-$context", $files, 'js' );
162 $src = $bundler->build();
163 $deps = [ 'jquery', 'rtsb-tipsy' ];
164
165 $builder_page_id = BuilderFns::builder_page_id_by_page();
166
167 if ( Fns::is_elementor_page( $builder_page_id ) ) {
168 $deps[] = 'imagesloaded';
169 }
170
171 $bundled_contexts[ $context ] = [
172 'handle' => "rtsb-bundled-$context",
173 'src' => $src,
174 'deps' => apply_filters( 'rtsb/optimizer/scripts/deps', $deps ),
175 'footer' => true,
176 ];
177 }
178
179 return $bundled_contexts;
180 }
181
182 /**
183 * Returns assets grouped by context with `global` inherited for each.
184 *
185 * @param string $type 'css' or 'js'.
186 *
187 * @return array
188 */
189 public function get_contextual_assets( $type ) {
190 $all_contexts = 'js' === $type ? $this->get_scripts_by_context() : $this->get_styles_by_context();
191
192 $normalized = array_map(
193 function ( $paths ) {
194 return array_unique( $paths );
195 },
196 $all_contexts
197 );
198
199 foreach ( $normalized as $context => $paths ) {
200 if ( 'global' === $context ) {
201 continue;
202 }
203
204 $normalized[ $context ] = array_unique(
205 array_merge(
206 $normalized['global'] ?? [],
207 $paths
208 )
209 );
210 }
211
212 return $normalized;
213 }
214
215 /**
216 * Returns scripts grouped by context.
217 *
218 * @return array
219 */
220 public function get_scripts_by_context() {
221 $context_scripts = [];
222
223 // Add core-init globally.
224 $core_path = $this->context()->get_assets_path( 'js/frontend/core-init.js' );
225 $context_scripts['global'][] = $core_path;
226
227 $shared_registry = apply_filters(
228 'rtsb/optimizer/scripts/shared/components',
229 [
230 'coupon' => [
231 'path' => rtsb()->get_assets_path( 'js/modules/shared/coupon.js' ),
232 'modules' => [ 'shopify-checkout', 'quick-checkout' ],
233 'widgets' => [ 'cart-coupon-form', 'coupon-form' ],
234 'context' => Fns::is_module_active( 'quick_checkout' ) ? [ 'global' ] : [ 'cart', 'checkout' ],
235 ],
236 'quantity' => [
237 'path' => rtsb()->get_assets_path( 'js/modules/shared/cart-quantity-handler.js' ),
238 'modules' => [ 'sticky-add-to-cart' ],
239 'widgets' => [ 'cart-table', 'product-add-to-cart' ],
240 'context' => Fns::is_module_active( 'quick_view' ) ? [ 'global' ] : [ 'product', 'cart' ],
241 ],
242 ]
243 );
244
245 $shared_loaded = [];
246
247 // Load module-based shared scripts.
248 foreach ( $this->enabled_modules_js as $module_slug => $module ) {
249 if ( ! $module['enabled'] ) {
250 continue;
251 }
252
253 foreach ( $shared_registry as $key => $data ) {
254 if ( in_array( $module_slug, $data['modules'], true ) ) {
255 $shared_loaded[ $key ] = $data;
256 }
257 }
258
259 $module_slug = apply_filters( 'rtsb/optimizer/module/remap', $module_slug );
260 $path = Fns::locate_asset( "js/modules/$module_slug.js" );
261
262 if ( $path ) {
263 $category = ! empty( $module['category'] ) ? $module['category'] : [ 'global' ];
264
265 foreach ( $category as $cat ) {
266 $context_scripts[ $cat ][] = $path;
267 }
268 }
269 }
270
271 // Elementor-specific widget scripts.
272 if ( defined( 'ELEMENTOR_VERSION' ) && Fns::should_load_elementor_scripts() ) {
273 foreach ( $this->enabled_widgets as $slug => $widget ) {
274 if ( ! $widget['enabled'] ) {
275 continue;
276 }
277
278 foreach ( $shared_registry as $key => $data ) {
279 if ( isset( $shared_loaded[ $key ] ) ) {
280 continue;
281 }
282
283 if ( in_array( $slug, $data['widgets'], true ) ) {
284 $shared_loaded[ $key ] = $data;
285 }
286 }
287
288 $path = Fns::locate_asset( "js/frontend/elementor/$slug.js" );
289
290 if ( $path ) {
291 $category = ! empty( $widget['category'] ) ? $widget['category'] : 'global';
292
293 if ( 'general' === $category ) {
294 $category = 'global';
295 }
296
297 $context_scripts[ $category ][] = $path;
298 }
299 }
300
301 $core_el = Fns::locate_asset( 'js/frontend/elementor/el-frontend-core.js' );
302
303 if ( $core_el ) {
304 $context_scripts['global'][] = $core_el;
305 }
306 }
307
308 foreach ( $shared_loaded as $data ) {
309 if ( empty( $data['path'] ) ) {
310 continue;
311 }
312
313 $contexts = $data['context'] ?? [ 'global' ];
314
315 foreach ( (array) $contexts as $ctx ) {
316 $context_scripts[ $ctx ][] = $data['path'];
317 }
318 }
319
320 $context_scripts = apply_filters(
321 'rtsb/optimizer/scripts/shared',
322 $context_scripts,
323 $this->enabled_modules,
324 $this->enabled_widgets
325 );
326
327 return apply_filters( 'rtsb/optimizer/scripts', $context_scripts );
328 }
329
330 /**
331 * Get structured JS scripts.
332 *
333 * @return array[]
334 */
335 public function get_scripts() {
336 $scripts = [];
337 $shared_scripts = [];
338 $shared_loaded = [];
339
340 // Load core.
341 $scripts[] = $this->context()->get_assets_path( 'js/frontend/core-init.js' );
342
343 $shared_registry = apply_filters(
344 'rtsb/optimizer/scripts/shared/components',
345 [
346 'coupon' => [
347 'path' => rtsb()->get_assets_path( 'js/modules/shared/coupon.js' ),
348 'modules' => [ 'shopify-checkout', 'quick-checkout' ],
349 'widgets' => [ 'cart-coupon-form', 'coupon-form' ],
350 ],
351 'quantity' => [
352 'path' => rtsb()->get_assets_path( 'js/modules/shared/cart-quantity-handler.js' ),
353 'modules' => [ 'sticky-add-to-cart' ],
354 'widgets' => [ 'cart-table', 'product-add-to-cart' ],
355 ],
356 ]
357 );
358
359 foreach ( $this->enabled_modules as $module ) {
360 foreach ( $shared_registry as $key => $data ) {
361 if ( in_array( $module, $data['modules'], true ) ) {
362 $shared_loaded[ $key ] = true;
363 }
364 }
365
366 $module = apply_filters( 'rtsb/optimizer/module/remap', $module );
367 $path = Fns::locate_asset( "js/modules/$module.js" );
368
369 if ( $path ) {
370 $scripts[] = $path;
371 }
372 }
373
374 if ( defined( 'ELEMENTOR_VERSION' ) && Fns::should_load_elementor_scripts() ) {
375 foreach ( $this->enabled_el_widgets as $widget ) {
376 foreach ( $shared_registry as $key => $data ) {
377 if ( isset( $shared_loaded[ $key ] ) ) {
378 continue;
379 }
380
381 if ( in_array( $widget, $data['widgets'], true ) ) {
382 $shared_loaded[ $key ] = true;
383 }
384 }
385
386 $path = Fns::locate_asset( "js/frontend/elementor/$widget.js" );
387
388 if ( $path ) {
389 $scripts[] = $path;
390 }
391 }
392
393 $scripts[] = Fns::locate_asset( 'js/frontend/elementor/el-frontend-core.js' );
394 }
395
396 foreach ( $shared_loaded as $key => $_ ) {
397 if ( isset( $shared_registry[ $key ]['path'] ) ) {
398 $shared_scripts[] = $shared_registry[ $key ]['path'];
399 }
400 }
401
402 $shared_scripts = apply_filters(
403 'rtsb/optimizer/scripts/shared',
404 $shared_scripts,
405 $this->enabled_modules,
406 $this->enabled_el_widgets
407 );
408
409 $scripts = array_merge( $scripts, array_unique( $shared_scripts ) );
410
411 return array_unique( apply_filters( 'rtsb/optimizer/scripts', $scripts ) );
412 }
413
414 /**
415 * Get structured CSS styles.
416 *
417 * @return array[]
418 */
419 public function get_styles() {
420 $elementor_styles = defined( 'ELEMENTOR_VERSION' ) && Fns::should_load_elementor_scripts()
421 ? $this->get_elementor_styles() : [];
422
423 return apply_filters(
424 'rtsb/optimizer/styles',
425 array_merge(
426 $this->get_base_styles(),
427 $this->get_module_styles(),
428 $elementor_styles
429 )
430 );
431 }
432
433 /**
434 * Get structured CSS styles.
435 *
436 * @return array[]
437 */
438 public function get_styles_by_context() {
439 $core = $this->get_base_styles_by_context();
440 $modules = $this->get_module_styles_by_context();
441 $elementor = defined( 'ELEMENTOR_VERSION' ) && Fns::should_load_elementor_scripts() ? $this->get_elementor_styles_by_context() : [];
442
443 $merged = [];
444
445 foreach ( [ $core, $modules, $elementor ] as $group ) {
446 foreach ( $group as $context => $paths ) {
447 foreach ( $paths as $file ) {
448 $merged[ $context ][] = $file;
449 }
450 }
451 }
452
453 return apply_filters( 'rtsb/optimizer/styles/by_context', $merged );
454 }
455
456 /**
457 * Get base styles.
458 *
459 * @return array
460 */
461 protected function get_base_styles() {
462 return apply_filters(
463 'rtsb/optimizer/styles/base',
464 [
465 rtsb()->get_assets_path( 'css/frontend/rtsb-fonts.css' ),
466 Fns::locate_asset( $this->dir . 'frontend/frontend-core' . $this->suffix . '.css' ),
467 ]
468 );
469 }
470
471 /**
472 * Get base styles.
473 *
474 * @return array
475 */
476 protected function get_base_styles_by_context() {
477 $styles = array_filter(
478 [
479 rtsb()->get_assets_path( 'css/frontend/rtsb-fonts.css' ),
480 Fns::locate_asset( $this->dir . 'frontend/frontend-core' . $this->suffix . '.css' ),
481 ]
482 );
483
484 return [
485 'global' => $styles,
486 ];
487 }
488
489 /**
490 * Get module styles.
491 *
492 * @return array
493 */
494 protected function get_module_styles() {
495 $styles = [];
496
497 if ( empty( $this->enabled_modules ) ) {
498 return $styles;
499 }
500
501 $styles = array_merge( $styles, $this->get_shared_module_styles() );
502
503 foreach ( $this->enabled_modules as $module ) {
504 $module = apply_filters( 'rtsb/optimizer/module/remap', $module );
505 $path = Fns::locate_asset( "{$this->dir}modules/$module$this->suffix.css" );
506
507 if ( $path ) {
508 $styles[] = $path;
509 }
510 }
511
512 $styles = apply_filters( 'rtsb/optimizer/styles/module', $styles );
513
514 return array_unique( $styles );
515 }
516
517 /**
518 * Get module styles.
519 *
520 * @return array
521 */
522 protected function get_module_styles_by_context() {
523 $content_styles = [];
524
525 if ( empty( $this->enabled_modules_css ) ) {
526 return $content_styles;
527 }
528
529 $content_styles = array_merge( $content_styles, $this->get_shared_module_styles() );
530
531 foreach ( $this->enabled_modules_css as $module_slug => $module ) {
532 if ( ! $module['enabled'] ) {
533 continue;
534 }
535
536 $module_slug = apply_filters( 'rtsb/optimizer/module/remap', $module_slug );
537 $path = Fns::locate_asset( "{$this->dir}modules/$module_slug$this->suffix.css" );
538
539 if ( $path ) {
540 $category = ! empty( $module['category'] ) ? $module['category'] : [ 'global' ];
541
542 foreach ( $category as $cat ) {
543 $content_styles[ $cat ][] = $path;
544 }
545 }
546 }
547
548 return apply_filters( 'rtsb/optimizer/styles/module', $content_styles );
549 }
550
551 /**
552 * Get Elementor styles.
553 *
554 * @return array
555 */
556 protected function get_elementor_styles() {
557 $styles = [ Fns::locate_asset( $this->dir . 'frontend/elementor/el-frontend-core' . $this->suffix . '.css' ) ];
558 $shared = $this->contextual_loading ? $this->get_elementor_shared_widget_styles_by_context() : $this->get_elementor_shared_widget_styles();
559 $styles = array_merge( $styles, $shared );
560
561 foreach ( $this->enabled_el_widgets as $widget ) {
562 $path = Fns::locate_asset( "{$this->dir}frontend/elementor/$widget$this->suffix.css" );
563
564 if ( $path ) {
565 $styles[] = $path;
566 }
567 }
568
569 return array_unique( $styles );
570 }
571
572 /**
573 * Get Elementor styles.
574 *
575 * @return array
576 */
577 protected function get_elementor_styles_by_context() {
578 $styles = [];
579
580 $styles['global'] = [
581 Fns::locate_asset( $this->dir . 'frontend/elementor/el-frontend-core' . $this->suffix . '.css' ),
582 ];
583
584 $shared_styles = $this->get_elementor_shared_widget_styles_by_context();
585
586 foreach ( $shared_styles as $context => $context_styles ) {
587 foreach ( (array) $context_styles as $style ) {
588 $styles[ $context ][] = $style;
589 }
590 }
591
592 foreach ( $this->enabled_widgets as $widget_slug => $info ) {
593 if ( empty( $info['enabled'] ) ) {
594 continue;
595 }
596
597 $contexts = $info['category'] ?? [ 'global' ];
598
599 foreach ( (array) $contexts as $ctx ) {
600 if ( 'general' === $ctx ) {
601 $ctx = 'global';
602 }
603
604 $path = Fns::locate_asset( "{$this->dir}frontend/elementor/{$widget_slug}{$this->suffix}.css" );
605
606 if ( $path ) {
607 $styles[ $ctx ][] = $path;
608 }
609 }
610 }
611
612 return $styles;
613 }
614
615 /**
616 * Get shared styles based on enabled modules.
617 *
618 * @return array
619 */
620 protected function get_shared_module_styles() {
621 $shared_styles_map = apply_filters(
622 'rtsb/optimizer/styles/modules/shared',
623 [
624 [
625 'modules' => [ 'wishlist', 'compare' ],
626 'file_name' => 'shared/module-buttons',
627 'context' => [ 'global' ],
628 ],
629 [
630 'modules' => [ 'quick-view', 'product-size-chart', 'quick-checkout' ],
631 'file_name' => 'shared/modal-layouts',
632 'context' => [ 'global' ],
633 ],
634 ]
635 );
636
637 // Add global shared.
638 $shared_by_components = $this->get_shared_style_components();
639
640 if ( ! $this->contextual_loading ) {
641 foreach ( $shared_by_components as $key => $data ) {
642 if ( ! empty( $data['modules'] ) && array_intersect( $data['modules'], $this->enabled_modules ) ) {
643 $shared_styles_map[] = [
644 'modules' => $data['modules'],
645 'file_name' => $data['file_name'],
646 ];
647 $this->shared_loaded[ $key ] = true;
648 }
649 }
650
651 $styles = [];
652
653 if ( empty( $this->enabled_modules ) ) {
654 return $styles;
655 }
656
657 foreach ( $shared_styles_map as $group ) {
658 if ( array_intersect( $group['modules'], $this->enabled_modules ) ) {
659 $styles[] = Fns::locate_asset( "{$this->dir}modules/{$group['file_name']}{$this->suffix}.css" );
660 }
661 }
662
663 return array_unique( $styles );
664 }
665
666 $enabled_widget_slugs = array_keys(
667 array_filter( $this->enabled_widgets, fn( $w ) => ! empty( $w['enabled'] ) )
668 );
669
670 $enabled_module_slugs = array_keys(
671 array_filter( $this->enabled_modules_css ?? [], fn( $m ) => ! empty( $m['enabled'] ) )
672 );
673
674 foreach ( $shared_by_components as $key => $data ) {
675 $modules = $data['modules'] ?? [];
676 $widgets = $data['widgets'] ?? [];
677
678 if (
679 array_intersect( $modules, $enabled_module_slugs ) ||
680 array_intersect( $widgets, $enabled_widget_slugs )
681 ) {
682 $shared_styles_map[] = [
683 'modules' => $modules,
684 'widgets' => $widgets,
685 'file_name' => $data['file_name'],
686 'context' => $data['context'] ?? [ 'global' ],
687 ];
688 $this->shared_loaded[ $key ] = true;
689 }
690 }
691
692 $styles = [];
693
694 foreach ( $shared_styles_map as $group ) {
695 $modules = $group['modules'] ?? [];
696 $widgets = $group['widgets'] ?? [];
697
698 $has_enabled_module = array_intersect( $modules, $enabled_module_slugs );
699 $has_enabled_widget = array_intersect( $widgets, $enabled_widget_slugs );
700
701 if ( ! $has_enabled_module && ! $has_enabled_widget ) {
702 continue;
703 }
704
705 foreach ( (array) $group['context'] as $ctx ) {
706 $path = Fns::locate_asset( "{$this->dir}modules/{$group['file_name']}{$this->suffix}.css" );
707
708 if ( $path ) {
709 $styles[ $ctx ][] = $path;
710 }
711 }
712 }
713
714 return $styles;
715 }
716
717 /**
718 * Get shared Elementor widget styles based on enabled widgets.
719 *
720 * @return array
721 */
722 protected function get_elementor_shared_widget_styles() {
723 $shared_styles_map = $this->get_elementor_shared_styles_map();
724
725 // Add global shared.
726 $shared_by_components = $this->get_shared_style_components();
727 $styles = [];
728
729 foreach ( $shared_by_components as $key => $data ) {
730 if ( isset( $this->shared_loaded[ $key ] ) ) {
731 continue;
732 }
733
734 if ( ! empty( $data['widgets'] ) && array_intersect( $data['widgets'], $this->enabled_el_widgets ) ) {
735 $path = Fns::locate_asset( "{$this->dir}modules/{$data['file_name']}{$this->suffix}.css" );
736
737 if ( $path ) {
738 $styles[] = $path;
739 }
740 }
741 }
742
743 foreach ( $shared_styles_map as $group ) {
744 if ( array_intersect( $group['widgets'], $this->enabled_el_widgets ) ) {
745 $styles[] = Fns::locate_asset( "{$this->dir}frontend/elementor/{$group['file_name']}$this->suffix.css" );
746 }
747 }
748
749 return array_unique( $styles );
750 }
751
752 /**
753 * Get shared Elementor widget styles based on enabled widgets.
754 *
755 * @return array
756 */
757 protected function get_elementor_shared_widget_styles_by_context() {
758 $shared_styles_map = $this->get_elementor_shared_styles_map();
759
760 // Add global shared.
761 $shared_by_components = $this->get_shared_style_components();
762 $styles = [];
763
764 $enabled_widget_slugs = array_keys(
765 array_filter(
766 $this->enabled_widgets ?? [],
767 fn( $widget ) => ! empty( $widget['enabled'] )
768 )
769 );
770
771 foreach ( $shared_by_components as $key => $data ) {
772 if ( isset( $this->shared_loaded[ $key ] ) ) {
773 continue;
774 }
775
776 if (
777 ! empty( $data['widgets'] ) &&
778 array_intersect( $data['widgets'], $enabled_widget_slugs )
779 ) {
780 $contexts = $data['context'] ?? [ 'global' ];
781
782 foreach ( (array) $contexts as $ctx ) {
783 if ( 'general' === $ctx ) {
784 $ctx = 'global';
785 }
786
787 $path = Fns::locate_asset( "{$this->dir}modules/{$data['file_name']}{$this->suffix}.css" );
788
789 if ( $path ) {
790 $styles[ $ctx ][] = $path;
791 }
792 }
793
794 $this->shared_loaded[ $key ] = true;
795 }
796 }
797
798 foreach ( $shared_styles_map as $group ) {
799 if (
800 ! empty( $group['widgets'] ) &&
801 array_intersect( $group['widgets'], $enabled_widget_slugs )
802 ) {
803 $contexts = $group['context'] ?? [ 'global' ];
804
805 if ( ! is_array( $contexts ) ) {
806 $contexts = [ $contexts ];
807 }
808
809 foreach ( $contexts as $ctx ) {
810 $path = Fns::locate_asset( "{$this->dir}frontend/elementor/{$group['file_name']}{$this->suffix}.css" );
811
812 if ( $path ) {
813 $styles[ $ctx ][] = $path;
814 }
815 }
816 }
817 }
818
819 return $styles;
820 }
821
822 /**
823 * Get Elementor shared styles map.
824 *
825 * @return array
826 */
827 private function get_elementor_shared_styles_map() {
828 return apply_filters(
829 'rtsb/optimizer/styles/elementor/shared',
830 [
831 [
832 'widgets' => [
833 'products-grid',
834 'products-list',
835 'products-slider',
836 'products-single-cateogory',
837 'product-cateogories',
838 'products-archive-catalog-custom',
839 'advanced-heading',
840 'dropcaps',
841 'shopbuilder-button',
842 'shopbuilder-cta',
843 'shopbuilder-info-box',
844 'shopbuilder-flip-box',
845 'shopbuilder-pricing-table',
846 'shopbuilder-counter',
847 'shopbuilder-countdown',
848 'shopbuilder-progress-bar',
849 'image-accordion',
850 'shopbuilder-faq',
851 'logo-slider-and-grid',
852 'testimonial',
853 'team-member',
854 'post-grid',
855 'post-list',
856 'upsells-product-custom',
857 'upsells-products-slider-custom',
858 'related-product-custom',
859 'related-products-slider-custom',
860 'cross-sells-custom',
861 'cross-sells-custom-slider',
862 ],
863 'file_name' => 'shared/el-grid',
864 'context' => [ 'global' ],
865 ],
866 [
867 'widgets' => [
868 'shopbuilder-button',
869 'shopbuilder-cta',
870 'shopbuilder-info-box',
871 'shopbuilder-flip-box',
872 'shopbuilder-pricing-table',
873 ],
874 'file_name' => 'shared/shopbuilder-button',
875 'context' => [ 'global' ],
876 ],
877 [
878 'widgets' => [
879 'products-grid',
880 'products-list',
881 'products-slider',
882 'products-single-cateogory',
883 'product-cateogories',
884 'products-archive-catalog-custom',
885 'upsells-product-custom',
886 'upsells-products-slider-custom',
887 'related-product-custom',
888 'related-products-slider-custom',
889 'cross-sells-custom',
890 'cross-sells-custom-slider',
891 ],
892 'file_name' => 'shared/el-general-product',
893 'context' => [ 'global' ],
894 ],
895 [
896 'widgets' => [
897 'products-grid',
898 'products-slider',
899 'products-archive-catalog-custom',
900 ],
901 'file_name' => 'shared/products-grid-slider',
902 'context' => [ 'global' ],
903 ],
904 [
905 'widgets' => [
906 'products-list',
907 'products-archive-catalog-custom',
908 ],
909 'file_name' => 'shared/products-list',
910 'context' => [ 'global' ],
911 ],
912 [
913 'widgets' => [ 'post-grid', 'post-list' ],
914 'file_name' => 'shared/post',
915 'context' => [ 'global' ],
916 ],
917 [
918 'widgets' => [
919 'products-archive-catalog',
920 'products-archive-catalog-custom',
921 ],
922 'file_name' => 'shared/products-archive',
923 'context' => [ 'shop' ],
924 ],
925 [
926 'widgets' => [
927 'product-title',
928 'product-description',
929 'product-short-description',
930 'product-images',
931 'product-onsale',
932 'product-additional-info',
933 'product-price',
934 'product-meta',
935 'product-categories',
936 'product-rating',
937 'product-tags',
938 'product-sku',
939 'product-stock',
940 'actions-button',
941 'product-add-to-cart',
942 'product-share',
943 'product-tabs',
944 'product-reviews',
945 'upsells-product',
946 'related-product',
947 ],
948 'file_name' => 'shared/single-product',
949 'context' => [ 'product' ],
950 ],
951 [
952 'widgets' => [
953 'cart-table',
954 'cart-totals',
955 'cross-sells',
956 ],
957 'file_name' => 'shared/cart',
958 'context' => [ 'cart' ],
959 ],
960 ]
961 );
962 }
963
964 /**
965 * Get enabled modules. Filterable.
966 *
967 * @return array
968 */
969 protected function get_enabled_modules() {
970 $module_list = Fns::get_modules_list();
971 $enabled = [];
972
973 foreach ( $module_list as $slug => $info ) {
974 if ( defined( 'RTWPVG_VERSION' ) && 'variation_gallery' === $slug ) {
975 continue;
976 }
977 if ( defined( 'RTWPVS_VERSION' ) && 'variation_swatches' === $slug ) {
978 continue;
979 }
980 if ( ! empty( $info['active'] ) ) {
981 $enabled[] = str_replace( '_', '-', sanitize_key( $slug ) );
982 }
983 }
984
985 return $enabled;
986 }
987
988 /**
989 * Get enabled modules JS with context.
990 *
991 * @return array
992 */
993 protected function get_enabled_modules_js_with_context() {
994 $module_list = Fns::get_modules_list();
995 $modules = [];
996 $context = apply_filters(
997 'rtsb/optimizer/modules/js_context',
998 [
999 'checkout_fields_editor' => [ 'checkout' ],
1000 'shopify_checkout' => [ 'checkout' ],
1001 ]
1002 );
1003
1004 foreach ( $module_list as $slug => $info ) {
1005 if ( defined( 'RTWPVG_VERSION' ) && 'variation_gallery' === $slug ) {
1006 continue;
1007 }
1008 if ( defined( 'RTWPVS_VERSION' ) && 'variation_swatches' === $slug ) {
1009 continue;
1010 }
1011 $clean_slug = str_replace( '_', '-', sanitize_key( $slug ) );
1012 $is_enabled = isset( $info['active'] ) && 'on' === $info['active'];
1013
1014 $modules[ $clean_slug ] = [
1015 'enabled' => $is_enabled,
1016 'category' => ! empty( $context[ $slug ] ) ? $context[ $slug ] : [ 'global' ],
1017 ];
1018 }
1019
1020 return $modules;
1021 }
1022
1023 /**
1024 * Get enabled modules CSS with context.
1025 *
1026 * @return array
1027 */
1028 protected function get_enabled_modules_css_with_context() {
1029 $module_list = Fns::get_modules_list();
1030 $modules = [];
1031 $context = apply_filters(
1032 'rtsb/optimizer/modules/css_context',
1033 [
1034 'checkout_fields_editor' => [ 'checkout' ],
1035 'shopify_checkout' => [ 'checkout' ],
1036 ]
1037 );
1038
1039 foreach ( $module_list as $slug => $info ) {
1040 if ( defined( 'RTWPVG_VERSION' ) && 'variation_gallery' === $slug ) {
1041 continue;
1042 }
1043 if ( defined( 'RTWPVS_VERSION' ) && 'variation_swatches' === $slug ) {
1044 continue;
1045 }
1046 $clean_slug = str_replace( '_', '-', sanitize_key( $slug ) );
1047 $is_enabled = isset( $info['active'] ) && 'on' === $info['active'];
1048
1049 $modules[ $clean_slug ] = [
1050 'enabled' => $is_enabled,
1051 'category' => ! empty( $context[ $slug ] ) ? $context[ $slug ] : [ 'global' ],
1052 ];
1053 }
1054
1055 return $modules;
1056 }
1057
1058 /**
1059 * Get enabled modules.
1060 *
1061 * @return array
1062 */
1063 protected function get_enabled_el_widgets() {
1064 $widget_data = Fns::get_widgets_list();
1065 $enabled = [];
1066
1067 foreach ( $widget_data as $slug => $info ) {
1068 if ( isset( $info['active'] ) && 'on' === $info['active'] ) {
1069 $enabled[] = str_replace( '_', '-', sanitize_key( $slug ) );
1070 }
1071 }
1072
1073 return $enabled;
1074 }
1075
1076 /**
1077 * Get enabled widgets with categories.
1078 *
1079 * @return array
1080 */
1081 protected function get_enabled_widgets_with_context() {
1082 $widget_list = Fns::get_widgets_list();
1083 $widgets = [];
1084
1085 foreach ( $widget_list as $slug => $info ) {
1086 $clean_slug = str_replace( '_', '-', sanitize_key( $slug ) );
1087 $is_enabled = isset( $info['active'] ) && 'on' === $info['active'];
1088
1089 $widgets[ $clean_slug ] = [
1090 'enabled' => $is_enabled,
1091 'category' => ! empty( $widget_list[ $slug ]['category'] ) ? $widget_list[ $slug ]['category'] : 'global',
1092 ];
1093 }
1094
1095 return $widgets;
1096 }
1097
1098 /**
1099 * Populate JS/CSS file arrays based on active modules.
1100 *
1101 * @return void
1102 */
1103 protected function collect_assets() {
1104 static $ran = false;
1105
1106 if ( $ran ) {
1107 return;
1108 }
1109
1110 $ran = true;
1111
1112 $this->js_files = $this->contextual_loading ? $this->get_scripts_by_context() : array_unique( $this->get_scripts() );
1113 $this->css_files = $this->contextual_loading ? $this->get_styles_by_context() : array_unique( $this->get_styles() );
1114 }
1115
1116 /**
1117 * Build and return the final bundled and minified CSS and JS.
1118 *
1119 * @return array
1120 */
1121 public function get_bundled_assets() {
1122 if ( null !== $this->bundled_assets ) {
1123 return $this->bundled_assets;
1124 }
1125
1126 $this->collect_assets();
1127
1128 $this->bundled_assets = [
1129 'js' => $this->contextual_loading ? $this->get_bundled_js_by_context() : $this->get_bundled_js(),
1130 'css' => $this->contextual_loading ? $this->get_bundled_css_by_context() : $this->get_bundled_css(),
1131 ];
1132
1133 return $this->bundled_assets;
1134 }
1135
1136 /**
1137 * Load optimized assets and return them by type
1138 *
1139 * @param string $type 'css' or 'js'.
1140 *
1141 * @return array
1142 */
1143 public function load_optimized_assets( $type = 'css' ) {
1144 if ( isset( self::$loaded_types[ $type ] ) ) {
1145 return [];
1146 }
1147
1148 $assets = [];
1149 $bundled = $this->get_bundled_assets();
1150 $context = Fns::detect_context();
1151
1152 if ( $this->contextual_loading ) {
1153 if ( 'js' === $type && ! empty( $bundled['js'] ) ) {
1154 $ctx = isset( $bundled['js'][ $context ] ) ? $context : 'global';
1155
1156 if ( $ctx ) {
1157 $assets = (array) $bundled['js'][ $ctx ];
1158 }
1159 }
1160
1161 if ( 'css' === $type && ! empty( $bundled['css'] ) ) {
1162 $ctx = isset( $bundled['css'][ $context ] ) ? $context : 'global';
1163
1164 if ( $ctx ) {
1165 $assets = (array) $bundled['css'][ $ctx ];
1166 }
1167 }
1168 } else {
1169 if ( 'js' === $type && ! empty( $bundled['js'] ) ) {
1170 $assets = (array) $this->bundled_assets['js'];
1171 }
1172
1173 if ( 'css' === $type && ! empty( $bundled['css'] ) ) {
1174 $assets = (array) $this->bundled_assets['css'];
1175 }
1176 }
1177
1178 self::$loaded_types[ $type ] = true;
1179
1180 return $assets;
1181 }
1182
1183 /**
1184 * Get bundled JS, with caching to prevent multiple builds.
1185 *
1186 * @return array|null
1187 */
1188 protected function get_bundled_js() {
1189 if ( null !== $this->bundled_js ) {
1190 return $this->bundled_js;
1191 }
1192
1193 if ( empty( $this->js_files ) ) {
1194 return null;
1195 }
1196
1197 $handle = 'rtsb-bundled';
1198 $bundler = new AssetBundler( $handle, $this->js_files, 'js' );
1199 $src = $bundler->build();
1200 $deps = [ 'jquery', 'rtsb-tipsy' ];
1201
1202 $builder_page_id = BuilderFns::builder_page_id_by_page();
1203
1204 if ( Fns::is_elementor_page( $builder_page_id ) ) {
1205 $deps[] = 'imagesloaded';
1206 }
1207
1208 $this->bundled_js = [
1209 'handle' => $handle,
1210 'src' => $src,
1211 'deps' => apply_filters( 'rtsb/optimizer/scripts/deps', $deps ),
1212 'footer' => true,
1213 ];
1214
1215 return $this->bundled_js;
1216 }
1217
1218 /**
1219 * Get bundled CSS, with caching to prevent multiple builds.
1220 *
1221 * @return array|null
1222 */
1223 protected function get_bundled_css() {
1224 if ( null !== $this->bundled_css ) {
1225 return $this->bundled_css;
1226 }
1227
1228 if ( empty( $this->css_files ) ) {
1229 return null;
1230 }
1231
1232 $bundler = new AssetBundler( 'rtsb-bundled', $this->css_files, 'css' );
1233 $src = $bundler->build();
1234
1235 $this->bundled_css = [
1236 'handle' => 'rtsb-bundled',
1237 'src' => $src,
1238 ];
1239
1240 return $this->bundled_css;
1241 }
1242
1243 /**
1244 * Get bundled CSS by context
1245 *
1246 * @return array|null
1247 */
1248 public function get_bundled_css_by_context() {
1249 $css_files_by_context = $this->get_contextual_assets( 'css' );
1250 $bundled_contexts = [];
1251
1252 foreach ( $css_files_by_context as $context => $files ) {
1253 if ( empty( $files ) ) {
1254 continue;
1255 }
1256
1257 $bundler = new AssetBundler( "rtsb-bundled-$context", $files, 'css' );
1258 $src = $bundler->build();
1259
1260 $bundled_contexts[ $context ] = [
1261 'handle' => "rtsb-bundled-$context",
1262 'src' => $src,
1263 ];
1264 }
1265
1266 return $bundled_contexts;
1267 }
1268
1269 /**
1270 * Forcefully regenerate bundled JS and CSS assets.
1271 *
1272 * @return array
1273 */
1274 public function regenerate_bundles() {
1275 $this->bundled_js = null;
1276 $this->bundled_css = null;
1277 $this->js_files = [];
1278 $this->css_files = [];
1279
1280 $this->collect_assets();
1281
1282 return [
1283 'js' => $this->contextual_loading ? $this->get_bundled_js_by_context() : $this->get_bundled_js(),
1284 'css' => $this->contextual_loading ? $this->get_bundled_css_by_context() : $this->get_bundled_css(),
1285 ];
1286 }
1287
1288 /**
1289 * Get context.
1290 *
1291 * @return object
1292 */
1293 private function context() {
1294 return ( function_exists( 'rtsbpro' ) && rtsb()->has_pro() ) ? rtsbpro() : rtsb();
1295 }
1296
1297 /**
1298 * Get shared style mappings for modules and widgets.
1299 *
1300 * @return array
1301 */
1302 private function get_shared_style_components() {
1303 return apply_filters(
1304 'rtsb/optimizer/styles/shared/components',
1305 [
1306 'checkout' => [
1307 'file_name' => 'shared/checkout',
1308 'modules' => [ 'quick-checkout', 'shopify-checkout' ],
1309 'widgets' => [
1310 'billing-form',
1311 'shipping-form',
1312 'order-notes',
1313 'order-review',
1314 'checkout-payment',
1315 'coupon-form',
1316 'checkout-login-form',
1317 'shipping-method',
1318 ],
1319 'context' => [ 'global' ],
1320 ],
1321 'slider' => [
1322 'file_name' => 'shared/slider-core',
1323 'modules' => [ 'variation-gallery' ],
1324 'widgets' => [
1325 'products-slider',
1326 'team-member',
1327 'upsells-products-slider-custom',
1328 'related-products-slider-custom',
1329 'cross-sells-custom-slider',
1330 'hero-slider',
1331 ],
1332 'context' => [ 'global' ],
1333 ],
1334 ]
1335 );
1336 }
1337 }
1338