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

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

1,319 lines 30.6 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-slider',
898 'team-member',
899 'upsells-products-slider-custom',
900 'related-products-slider-custom',
901 'cross-sells-custom-slider',
902 'hero-slider',
903 ],
904 'file_name' => 'shared/slider-core',
905 'context' => [ 'global' ],
906 ],
907 [
908 'widgets' => [
909 'products-grid',
910 'products-slider',
911 'products-archive-catalog-custom',
912 ],
913 'file_name' => 'shared/products-grid-slider',
914 'context' => [ 'global' ],
915 ],
916 [
917 'widgets' => [
918 'products-list',
919 'products-archive-catalog-custom',
920 ],
921 'file_name' => 'shared/products-list',
922 'context' => [ 'global' ],
923 ],
924 [
925 'widgets' => [ 'post-grid', 'post-list' ],
926 'file_name' => 'shared/post',
927 'context' => [ 'global' ],
928 ],
929 [
930 'widgets' => [
931 'products-archive-catalog',
932 'products-archive-catalog-custom',
933 ],
934 'file_name' => 'shared/products-archive',
935 'context' => [ 'shop' ],
936 ],
937 [
938 'widgets' => [
939 'product-title',
940 'product-description',
941 'product-short-description',
942 'product-images',
943 'product-onsale',
944 'product-additional-info',
945 'product-price',
946 'product-meta',
947 'product-categories',
948 'product-rating',
949 'product-tags',
950 'product-sku',
951 'product-stock',
952 'actions-button',
953 'product-add-to-cart',
954 'product-share',
955 'product-tabs',
956 'product-reviews',
957 'upsells-product',
958 'related-product',
959 ],
960 'file_name' => 'shared/single-product',
961 'context' => [ 'product' ],
962 ],
963 [
964 'widgets' => [
965 'cart-table',
966 'cart-totals',
967 'cross-sells',
968 ],
969 'file_name' => 'shared/cart',
970 'context' => [ 'cart' ],
971 ],
972 ]
973 );
974 }
975
976 /**
977 * Get enabled modules. Filterable.
978 *
979 * @return array
980 */
981 protected function get_enabled_modules() {
982 $module_list = Fns::get_modules_list();
983 $enabled = [];
984
985 foreach ( $module_list as $slug => $info ) {
986 if ( ! empty( $info['active'] ) ) {
987 $enabled[] = str_replace( '_', '-', sanitize_key( $slug ) );
988 }
989 }
990
991 return $enabled;
992 }
993
994 /**
995 * Get enabled modules JS with context.
996 *
997 * @return array
998 */
999 protected function get_enabled_modules_js_with_context() {
1000 $module_list = Fns::get_modules_list();
1001 $modules = [];
1002 $context = apply_filters(
1003 'rtsb/optimizer/modules/js_context',
1004 [
1005 'checkout_fields_editor' => [ 'checkout' ],
1006 'shopify_checkout' => [ 'checkout' ],
1007 ]
1008 );
1009
1010 foreach ( $module_list as $slug => $info ) {
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 $clean_slug = str_replace( '_', '-', sanitize_key( $slug ) );
1041 $is_enabled = isset( $info['active'] ) && 'on' === $info['active'];
1042
1043 $modules[ $clean_slug ] = [
1044 'enabled' => $is_enabled,
1045 'category' => ! empty( $context[ $slug ] ) ? $context[ $slug ] : [ 'global' ],
1046 ];
1047 }
1048
1049 return $modules;
1050 }
1051
1052 /**
1053 * Get enabled modules.
1054 *
1055 * @return array
1056 */
1057 protected function get_enabled_el_widgets() {
1058 $widget_data = Fns::get_widgets_list();
1059 $enabled = [];
1060
1061 foreach ( $widget_data as $slug => $info ) {
1062 if ( isset( $info['active'] ) && 'on' === $info['active'] ) {
1063 $enabled[] = str_replace( '_', '-', sanitize_key( $slug ) );
1064 }
1065 }
1066
1067 return $enabled;
1068 }
1069
1070 /**
1071 * Get enabled widgets with categories.
1072 *
1073 * @return array
1074 */
1075 protected function get_enabled_widgets_with_context() {
1076 $widget_list = Fns::get_widgets_list();
1077 $widgets = [];
1078
1079 foreach ( $widget_list as $slug => $info ) {
1080 $clean_slug = str_replace( '_', '-', sanitize_key( $slug ) );
1081 $is_enabled = isset( $info['active'] ) && 'on' === $info['active'];
1082
1083 $widgets[ $clean_slug ] = [
1084 'enabled' => $is_enabled,
1085 'category' => ! empty( $widget_list[ $slug ]['category'] ) ? $widget_list[ $slug ]['category'] : 'global',
1086 ];
1087 }
1088
1089 return $widgets;
1090 }
1091
1092 /**
1093 * Populate JS/CSS file arrays based on active modules.
1094 *
1095 * @return void
1096 */
1097 protected function collect_assets() {
1098 static $ran = false;
1099
1100 if ( $ran ) {
1101 return;
1102 }
1103
1104 $ran = true;
1105
1106 $this->js_files = $this->contextual_loading ? $this->get_scripts_by_context() : array_unique( $this->get_scripts() );
1107 $this->css_files = $this->contextual_loading ? $this->get_styles_by_context() : array_unique( $this->get_styles() );
1108 }
1109
1110 /**
1111 * Build and return the final bundled and minified CSS and JS.
1112 *
1113 * @return array
1114 */
1115 public function get_bundled_assets() {
1116 if ( null !== $this->bundled_assets ) {
1117 return $this->bundled_assets;
1118 }
1119
1120 $this->collect_assets();
1121
1122 $this->bundled_assets = [
1123 'js' => $this->contextual_loading ? $this->get_bundled_js_by_context() : $this->get_bundled_js(),
1124 'css' => $this->contextual_loading ? $this->get_bundled_css_by_context() : $this->get_bundled_css(),
1125 ];
1126
1127 return $this->bundled_assets;
1128 }
1129
1130 /**
1131 * Load optimized assets and return them by type
1132 *
1133 * @param string $type 'css' or 'js'.
1134 *
1135 * @return array
1136 */
1137 public function load_optimized_assets( $type = 'css' ) {
1138 if ( isset( self::$loaded_types[ $type ] ) ) {
1139 return [];
1140 }
1141
1142 $assets = [];
1143 $bundled = $this->get_bundled_assets();
1144 $context = Fns::detect_context();
1145
1146 if ( $this->contextual_loading ) {
1147 if ( 'js' === $type && ! empty( $bundled['js'] ) ) {
1148 $ctx = isset( $bundled['js'][ $context ] ) ? $context : 'global';
1149
1150 if ( $ctx ) {
1151 $assets = (array) $bundled['js'][ $ctx ];
1152 }
1153 }
1154
1155 if ( 'css' === $type && ! empty( $bundled['css'] ) ) {
1156 $ctx = isset( $bundled['css'][ $context ] ) ? $context : 'global';
1157
1158 if ( $ctx ) {
1159 $assets = (array) $bundled['css'][ $ctx ];
1160 }
1161 }
1162 } else {
1163 if ( 'js' === $type && ! empty( $bundled['js'] ) ) {
1164 $assets = (array) $this->bundled_assets['js'];
1165 }
1166
1167 if ( 'css' === $type && ! empty( $bundled['css'] ) ) {
1168 $assets = (array) $this->bundled_assets['css'];
1169 }
1170 }
1171
1172 self::$loaded_types[ $type ] = true;
1173
1174 return $assets;
1175 }
1176
1177 /**
1178 * Get bundled JS, with caching to prevent multiple builds.
1179 *
1180 * @return array|null
1181 */
1182 protected function get_bundled_js() {
1183 if ( null !== $this->bundled_js ) {
1184 return $this->bundled_js;
1185 }
1186
1187 if ( empty( $this->js_files ) ) {
1188 return null;
1189 }
1190
1191 $handle = 'rtsb-bundled';
1192 $bundler = new AssetBundler( $handle, $this->js_files, 'js' );
1193 $src = $bundler->build();
1194 $deps = [ 'jquery', 'rtsb-tipsy' ];
1195
1196 $builder_page_id = BuilderFns::builder_page_id_by_page();
1197
1198 if ( Fns::is_elementor_page( $builder_page_id ) ) {
1199 $deps[] = 'imagesloaded';
1200 }
1201
1202 $this->bundled_js = [
1203 'handle' => $handle,
1204 'src' => $src,
1205 'deps' => apply_filters( 'rtsb/optimizer/scripts/deps', $deps ),
1206 'footer' => true,
1207 ];
1208
1209 return $this->bundled_js;
1210 }
1211
1212 /**
1213 * Get bundled CSS, with caching to prevent multiple builds.
1214 *
1215 * @return array|null
1216 */
1217 protected function get_bundled_css() {
1218 if ( null !== $this->bundled_css ) {
1219 return $this->bundled_css;
1220 }
1221
1222 if ( empty( $this->css_files ) ) {
1223 return null;
1224 }
1225
1226 $bundler = new AssetBundler( 'rtsb-bundled', $this->css_files, 'css' );
1227 $src = $bundler->build();
1228
1229 $this->bundled_css = [
1230 'handle' => 'rtsb-bundled',
1231 'src' => $src,
1232 ];
1233
1234 return $this->bundled_css;
1235 }
1236
1237 /**
1238 * Get bundled CSS by context
1239 *
1240 * @return array|null
1241 */
1242 public function get_bundled_css_by_context() {
1243 $css_files_by_context = $this->get_contextual_assets( 'css' );
1244 $bundled_contexts = [];
1245
1246 foreach ( $css_files_by_context as $context => $files ) {
1247 if ( empty( $files ) ) {
1248 continue;
1249 }
1250
1251 $bundler = new AssetBundler( "rtsb-bundled-$context", $files, 'css' );
1252 $src = $bundler->build();
1253
1254 $bundled_contexts[ $context ] = [
1255 'handle' => "rtsb-bundled-$context",
1256 'src' => $src,
1257 ];
1258 }
1259
1260 return $bundled_contexts;
1261 }
1262
1263 /**
1264 * Forcefully regenerate bundled JS and CSS assets.
1265 *
1266 * @return array
1267 */
1268 public function regenerate_bundles() {
1269 $this->bundled_js = null;
1270 $this->bundled_css = null;
1271 $this->js_files = [];
1272 $this->css_files = [];
1273
1274 $this->collect_assets();
1275
1276 return [
1277 'js' => $this->contextual_loading ? $this->get_bundled_js_by_context() : $this->get_bundled_js(),
1278 'css' => $this->contextual_loading ? $this->get_bundled_css_by_context() : $this->get_bundled_css(),
1279 ];
1280 }
1281
1282 /**
1283 * Get context.
1284 *
1285 * @return object
1286 */
1287 private function context() {
1288 return ( function_exists( 'rtsbpro' ) && rtsb()->has_pro() ) ? rtsbpro() : rtsb();
1289 }
1290
1291 /**
1292 * Get shared style mappings for modules and widgets.
1293 *
1294 * @return array
1295 */
1296 private function get_shared_style_components() {
1297 return apply_filters(
1298 'rtsb/optimizer/styles/shared/components',
1299 [
1300 'checkout' => [
1301 'file_name' => 'shared/checkout',
1302 'modules' => [ 'quick-checkout', 'shopify-checkout' ],
1303 'widgets' => [
1304 'billing-form',
1305 'shipping-form',
1306 'order-notes',
1307 'order-review',
1308 'checkout-payment',
1309 'coupon-form',
1310 'checkout-login-form',
1311 'shipping-method',
1312 ],
1313 'context' => [ 'global' ],
1314 ],
1315 ]
1316 );
1317 }
1318 }
1319