PluginProbe
ShopBuilder – WooCommerce Builder For Elementor / trunk
ShopBuilder – WooCommerce Builder For Elementor vtrunk
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 2.3.0 All 62 releases
shopbuilder / app / Controllers / AssetRegistry.php

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

1,334 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 return apply_filters( 'rtsb/optimizer/scripts', $context_scripts );
327 }
328
329 /**
330 * Get structured JS scripts.
331 *
332 * @return array[]
333 */
334 public function get_scripts() {
335 $scripts = [];
336 $shared_scripts = [];
337 $shared_loaded = [];
338
339 // Load core.
340 $scripts[] = $this->context()->get_assets_path( 'js/frontend/core-init.js' );
341
342 $shared_registry = apply_filters(
343 'rtsb/optimizer/scripts/shared/components',
344 [
345 'coupon' => [
346 'path' => rtsb()->get_assets_path( 'js/modules/shared/coupon.js' ),
347 'modules' => [ 'shopify-checkout', 'quick-checkout' ],
348 'widgets' => [ 'cart-coupon-form', 'coupon-form' ],
349 ],
350 'quantity' => [
351 'path' => rtsb()->get_assets_path( 'js/modules/shared/cart-quantity-handler.js' ),
352 'modules' => [ 'sticky-add-to-cart' ],
353 'widgets' => [ 'cart-table', 'product-add-to-cart' ],
354 ],
355 ]
356 );
357
358 foreach ( $this->enabled_modules as $module ) {
359 foreach ( $shared_registry as $key => $data ) {
360 if ( in_array( $module, $data['modules'], true ) ) {
361 $shared_loaded[ $key ] = true;
362 }
363 }
364
365 $module = apply_filters( 'rtsb/optimizer/module/remap', $module );
366 $path = Fns::locate_asset( "js/modules/$module.js" );
367
368 if ( $path ) {
369 $scripts[] = $path;
370 }
371 }
372
373 if ( defined( 'ELEMENTOR_VERSION' ) && Fns::should_load_elementor_scripts() ) {
374 foreach ( $this->enabled_el_widgets as $widget ) {
375 foreach ( $shared_registry as $key => $data ) {
376 if ( isset( $shared_loaded[ $key ] ) ) {
377 continue;
378 }
379
380 if ( in_array( $widget, $data['widgets'], true ) ) {
381 $shared_loaded[ $key ] = true;
382 }
383 }
384
385 $path = Fns::locate_asset( "js/frontend/elementor/$widget.js" );
386
387 if ( $path ) {
388 $scripts[] = $path;
389 }
390 }
391
392 $scripts[] = Fns::locate_asset( 'js/frontend/elementor/el-frontend-core.js' );
393 }
394
395 foreach ( $shared_loaded as $key => $_ ) {
396 if ( isset( $shared_registry[ $key ]['path'] ) ) {
397 $shared_scripts[] = $shared_registry[ $key ]['path'];
398 }
399 }
400
401 $shared_scripts = apply_filters(
402 'rtsb/optimizer/scripts/shared',
403 $shared_scripts,
404 $this->enabled_modules,
405 $this->enabled_el_widgets
406 );
407
408 $scripts = array_merge( $scripts, array_unique( $shared_scripts ) );
409
410 return array_unique( apply_filters( 'rtsb/optimizer/scripts', $scripts ) );
411 }
412
413 /**
414 * Get structured CSS styles.
415 *
416 * @return array[]
417 */
418 public function get_styles() {
419 $elementor_styles = defined( 'ELEMENTOR_VERSION' ) && Fns::should_load_elementor_scripts()
420 ? $this->get_elementor_styles() : [];
421
422 return apply_filters(
423 'rtsb/optimizer/styles',
424 array_merge(
425 $this->get_base_styles(),
426 $this->get_module_styles(),
427 $elementor_styles
428 )
429 );
430 }
431
432 /**
433 * Get structured CSS styles.
434 *
435 * @return array[]
436 */
437 public function get_styles_by_context() {
438 $core = $this->get_base_styles_by_context();
439 $modules = $this->get_module_styles_by_context();
440 $elementor = defined( 'ELEMENTOR_VERSION' ) && Fns::should_load_elementor_scripts() ? $this->get_elementor_styles_by_context() : [];
441
442 $merged = [];
443
444 foreach ( [ $core, $modules, $elementor ] as $group ) {
445 foreach ( $group as $context => $paths ) {
446 foreach ( $paths as $file ) {
447 $merged[ $context ][] = $file;
448 }
449 }
450 }
451
452 return apply_filters( 'rtsb/optimizer/styles/by_context', $merged );
453 }
454
455 /**
456 * Get base styles.
457 *
458 * @return array
459 */
460 protected function get_base_styles() {
461 return apply_filters(
462 'rtsb/optimizer/styles/base',
463 [
464 rtsb()->get_assets_path( 'css/frontend/rtsb-fonts.css' ),
465 Fns::locate_asset( $this->dir . 'frontend/frontend-core' . $this->suffix . '.css' ),
466 ]
467 );
468 }
469
470 /**
471 * Get base styles.
472 *
473 * @return array
474 */
475 protected function get_base_styles_by_context() {
476 $styles = array_filter(
477 [
478 rtsb()->get_assets_path( 'css/frontend/rtsb-fonts.css' ),
479 Fns::locate_asset( $this->dir . 'frontend/frontend-core' . $this->suffix . '.css' ),
480 ]
481 );
482
483 return [
484 'global' => $styles,
485 ];
486 }
487
488 /**
489 * Get module styles.
490 *
491 * @return array
492 */
493 protected function get_module_styles() {
494 $styles = [];
495
496 if ( empty( $this->enabled_modules ) ) {
497 return $styles;
498 }
499
500 $styles = array_merge( $styles, $this->get_shared_module_styles() );
501
502 foreach ( $this->enabled_modules as $module ) {
503 $module = apply_filters( 'rtsb/optimizer/module/remap', $module );
504 $path = Fns::locate_asset( "{$this->dir}modules/$module$this->suffix.css" );
505
506 if ( $path ) {
507 $styles[] = $path;
508 }
509 }
510
511 $styles = apply_filters( 'rtsb/optimizer/styles/module', $styles );
512
513 return array_unique( $styles );
514 }
515
516 /**
517 * Get module styles.
518 *
519 * @return array
520 */
521 protected function get_module_styles_by_context() {
522 $content_styles = [];
523
524 if ( empty( $this->enabled_modules_css ) ) {
525 return $content_styles;
526 }
527
528 $content_styles = array_merge( $content_styles, $this->get_shared_module_styles() );
529
530 foreach ( $this->enabled_modules_css as $module_slug => $module ) {
531 if ( ! $module['enabled'] ) {
532 continue;
533 }
534
535 $module_slug = apply_filters( 'rtsb/optimizer/module/remap', $module_slug );
536 $path = Fns::locate_asset( "{$this->dir}modules/$module_slug$this->suffix.css" );
537
538 if ( $path ) {
539 $category = ! empty( $module['category'] ) ? $module['category'] : [ 'global' ];
540
541 foreach ( $category as $cat ) {
542 $content_styles[ $cat ][] = $path;
543 }
544 }
545 }
546
547 return apply_filters( 'rtsb/optimizer/styles/module', $content_styles );
548 }
549
550 /**
551 * Get Elementor styles.
552 *
553 * @return array
554 */
555 protected function get_elementor_styles() {
556 $styles = [ Fns::locate_asset( $this->dir . 'frontend/elementor/el-frontend-core' . $this->suffix . '.css' ) ];
557 $shared = $this->contextual_loading ? $this->get_elementor_shared_widget_styles_by_context() : $this->get_elementor_shared_widget_styles();
558 $styles = array_merge( $styles, $shared );
559
560 foreach ( $this->enabled_el_widgets as $widget ) {
561 $path = Fns::locate_asset( "{$this->dir}frontend/elementor/$widget$this->suffix.css" );
562
563 if ( $path ) {
564 $styles[] = $path;
565 }
566 }
567
568 return array_unique( $styles );
569 }
570
571 /**
572 * Get Elementor styles.
573 *
574 * @return array
575 */
576 protected function get_elementor_styles_by_context() {
577 $styles = [];
578
579 $styles['global'] = [
580 Fns::locate_asset( $this->dir . 'frontend/elementor/el-frontend-core' . $this->suffix . '.css' ),
581 ];
582
583 $shared_styles = $this->get_elementor_shared_widget_styles_by_context();
584
585 foreach ( $shared_styles as $context => $context_styles ) {
586 foreach ( (array) $context_styles as $style ) {
587 $styles[ $context ][] = $style;
588 }
589 }
590
591 foreach ( $this->enabled_widgets as $widget_slug => $info ) {
592 if ( empty( $info['enabled'] ) ) {
593 continue;
594 }
595
596 $contexts = $info['category'] ?? [ 'global' ];
597
598 foreach ( (array) $contexts as $ctx ) {
599 if ( 'general' === $ctx ) {
600 $ctx = 'global';
601 }
602
603 $path = Fns::locate_asset( "{$this->dir}frontend/elementor/{$widget_slug}{$this->suffix}.css" );
604
605 if ( $path ) {
606 $styles[ $ctx ][] = $path;
607 }
608 }
609 }
610
611 return $styles;
612 }
613
614 /**
615 * Get shared styles based on enabled modules.
616 *
617 * @return array
618 */
619 protected function get_shared_module_styles() {
620 $shared_styles_map = apply_filters(
621 'rtsb/optimizer/styles/modules/shared',
622 [
623 [
624 'modules' => [ 'wishlist', 'compare' ],
625 'file_name' => 'shared/module-buttons',
626 'context' => [ 'global' ],
627 ],
628 [
629 'modules' => [ 'quick-view', 'product-size-chart', 'quick-checkout' ],
630 'file_name' => 'shared/modal-layouts',
631 'context' => [ 'global' ],
632 ],
633 ]
634 );
635
636 // Add global shared.
637 $shared_by_components = $this->get_shared_style_components();
638
639 if ( ! $this->contextual_loading ) {
640 foreach ( $shared_by_components as $key => $data ) {
641 if ( ! empty( $data['modules'] ) && array_intersect( $data['modules'], $this->enabled_modules ) ) {
642 $shared_styles_map[] = [
643 'modules' => $data['modules'],
644 'file_name' => $data['file_name'],
645 ];
646 $this->shared_loaded[ $key ] = true;
647 }
648 }
649
650 $styles = [];
651
652 if ( empty( $this->enabled_modules ) ) {
653 return $styles;
654 }
655
656 foreach ( $shared_styles_map as $group ) {
657 if ( array_intersect( $group['modules'], $this->enabled_modules ) ) {
658 $styles[] = Fns::locate_asset( "{$this->dir}modules/{$group['file_name']}{$this->suffix}.css" );
659 }
660 }
661
662 return array_unique( $styles );
663 }
664
665 $enabled_widget_slugs = array_keys(
666 array_filter( $this->enabled_widgets, fn( $w ) => ! empty( $w['enabled'] ) )
667 );
668
669 $enabled_module_slugs = array_keys(
670 array_filter( $this->enabled_modules_css ?? [], fn( $m ) => ! empty( $m['enabled'] ) )
671 );
672
673 foreach ( $shared_by_components as $key => $data ) {
674 $modules = $data['modules'] ?? [];
675 $widgets = $data['widgets'] ?? [];
676
677 if (
678 array_intersect( $modules, $enabled_module_slugs ) ||
679 array_intersect( $widgets, $enabled_widget_slugs )
680 ) {
681 $shared_styles_map[] = [
682 'modules' => $modules,
683 'widgets' => $widgets,
684 'file_name' => $data['file_name'],
685 'context' => $data['context'] ?? [ 'global' ],
686 ];
687 $this->shared_loaded[ $key ] = true;
688 }
689 }
690
691 $styles = [];
692
693 foreach ( $shared_styles_map as $group ) {
694 $modules = $group['modules'] ?? [];
695 $widgets = $group['widgets'] ?? [];
696
697 $has_enabled_module = array_intersect( $modules, $enabled_module_slugs );
698 $has_enabled_widget = array_intersect( $widgets, $enabled_widget_slugs );
699
700 if ( ! $has_enabled_module && ! $has_enabled_widget ) {
701 continue;
702 }
703
704 foreach ( (array) $group['context'] as $ctx ) {
705 $path = Fns::locate_asset( "{$this->dir}modules/{$group['file_name']}{$this->suffix}.css" );
706
707 if ( $path ) {
708 $styles[ $ctx ][] = $path;
709 }
710 }
711 }
712
713 return $styles;
714 }
715
716 /**
717 * Get shared Elementor widget styles based on enabled widgets.
718 *
719 * @return array
720 */
721 protected function get_elementor_shared_widget_styles() {
722 $shared_styles_map = $this->get_elementor_shared_styles_map();
723
724 // Add global shared.
725 $shared_by_components = $this->get_shared_style_components();
726 $styles = [];
727
728 foreach ( $shared_by_components as $key => $data ) {
729 if ( isset( $this->shared_loaded[ $key ] ) ) {
730 continue;
731 }
732
733 if ( ! empty( $data['widgets'] ) && array_intersect( $data['widgets'], $this->enabled_el_widgets ) ) {
734 $path = Fns::locate_asset( "{$this->dir}modules/{$data['file_name']}{$this->suffix}.css" );
735
736 if ( $path ) {
737 $styles[] = $path;
738 }
739 }
740 }
741
742 foreach ( $shared_styles_map as $group ) {
743 if ( array_intersect( $group['widgets'], $this->enabled_el_widgets ) ) {
744 $styles[] = Fns::locate_asset( "{$this->dir}frontend/elementor/{$group['file_name']}$this->suffix.css" );
745 }
746 }
747
748 return array_unique( $styles );
749 }
750
751 /**
752 * Get shared Elementor widget styles based on enabled widgets.
753 *
754 * @return array
755 */
756 protected function get_elementor_shared_widget_styles_by_context() {
757 $shared_styles_map = $this->get_elementor_shared_styles_map();
758
759 // Add global shared.
760 $shared_by_components = $this->get_shared_style_components();
761 $styles = [];
762
763 $enabled_widget_slugs = array_keys(
764 array_filter(
765 $this->enabled_widgets ?? [],
766 fn( $widget ) => ! empty( $widget['enabled'] )
767 )
768 );
769
770 foreach ( $shared_by_components as $key => $data ) {
771 if ( isset( $this->shared_loaded[ $key ] ) ) {
772 continue;
773 }
774
775 if (
776 ! empty( $data['widgets'] ) &&
777 array_intersect( $data['widgets'], $enabled_widget_slugs )
778 ) {
779 $contexts = $data['context'] ?? [ 'global' ];
780
781 foreach ( (array) $contexts as $ctx ) {
782 if ( 'general' === $ctx ) {
783 $ctx = 'global';
784 }
785
786 $path = Fns::locate_asset( "{$this->dir}modules/{$data['file_name']}{$this->suffix}.css" );
787
788 if ( $path ) {
789 $styles[ $ctx ][] = $path;
790 }
791 }
792
793 $this->shared_loaded[ $key ] = true;
794 }
795 }
796
797 foreach ( $shared_styles_map as $group ) {
798 if (
799 ! empty( $group['widgets'] ) &&
800 array_intersect( $group['widgets'], $enabled_widget_slugs )
801 ) {
802 $contexts = $group['context'] ?? [ 'global' ];
803
804 if ( ! is_array( $contexts ) ) {
805 $contexts = [ $contexts ];
806 }
807
808 foreach ( $contexts as $ctx ) {
809 $path = Fns::locate_asset( "{$this->dir}frontend/elementor/{$group['file_name']}{$this->suffix}.css" );
810
811 if ( $path ) {
812 $styles[ $ctx ][] = $path;
813 }
814 }
815 }
816 }
817
818 return $styles;
819 }
820
821 /**
822 * Get Elementor shared styles map.
823 *
824 * @return array
825 */
826 private function get_elementor_shared_styles_map() {
827 return apply_filters(
828 'rtsb/optimizer/styles/elementor/shared',
829 [
830 [
831 'widgets' => [
832 'products-grid',
833 'products-list',
834 'products-slider',
835 'products-single-cateogory',
836 'product-cateogories',
837 'products-archive-catalog-custom',
838 'advanced-heading',
839 'dropcaps',
840 'shopbuilder-button',
841 'shopbuilder-cta',
842 'shopbuilder-info-box',
843 'shopbuilder-flip-box',
844 'shopbuilder-pricing-table',
845 'shopbuilder-counter',
846 'shopbuilder-countdown',
847 'shopbuilder-progress-bar',
848 'image-accordion',
849 'shopbuilder-faq',
850 'logo-slider-and-grid',
851 'testimonial',
852 'team-member',
853 'post-grid',
854 'post-list',
855 'upsells-product-custom',
856 'upsells-products-slider-custom',
857 'related-product-custom',
858 'related-products-slider-custom',
859 'cross-sells-custom',
860 'cross-sells-custom-slider',
861 ],
862 'file_name' => 'shared/el-grid',
863 'context' => [ 'global' ],
864 ],
865 [
866 'widgets' => [
867 'shopbuilder-button',
868 'shopbuilder-cta',
869 'shopbuilder-info-box',
870 'shopbuilder-flip-box',
871 'shopbuilder-pricing-table',
872 ],
873 'file_name' => 'shared/shopbuilder-button',
874 'context' => [ 'global' ],
875 ],
876 [
877 'widgets' => [
878 'products-grid',
879 'products-list',
880 'products-slider',
881 'products-single-cateogory',
882 'product-cateogories',
883 'products-archive-catalog-custom',
884 'upsells-product-custom',
885 'upsells-products-slider-custom',
886 'related-product-custom',
887 'related-products-slider-custom',
888 'cross-sells-custom',
889 'cross-sells-custom-slider',
890 ],
891 'file_name' => 'shared/el-general-product',
892 'context' => [ 'global' ],
893 ],
894 [
895 'widgets' => [
896 'products-grid',
897 'products-slider',
898 'products-archive-catalog-custom',
899 ],
900 'file_name' => 'shared/products-grid-slider',
901 'context' => [ 'global' ],
902 ],
903 [
904 'widgets' => [
905 'products-list',
906 'products-archive-catalog-custom',
907 ],
908 'file_name' => 'shared/products-list',
909 'context' => [ 'global' ],
910 ],
911 [
912 'widgets' => [ 'post-grid', 'post-list' ],
913 'file_name' => 'shared/post',
914 'context' => [ 'global' ],
915 ],
916 [
917 'widgets' => [
918 'products-archive-catalog',
919 'products-archive-catalog-custom',
920 ],
921 'file_name' => 'shared/products-archive',
922 'context' => [ 'shop' ],
923 ],
924 [
925 'widgets' => [
926 'product-title',
927 'product-description',
928 'product-short-description',
929 'product-images',
930 'product-onsale',
931 'product-additional-info',
932 'product-price',
933 'product-meta',
934 'product-categories',
935 'product-rating',
936 'product-tags',
937 'product-sku',
938 'product-stock',
939 'actions-button',
940 'product-add-to-cart',
941 'product-share',
942 'product-tabs',
943 'product-reviews',
944 'upsells-product',
945 'related-product',
946 ],
947 'file_name' => 'shared/single-product',
948 'context' => [ 'product' ],
949 ],
950 [
951 'widgets' => [
952 'cart-table',
953 'cart-totals',
954 'cross-sells',
955 ],
956 'file_name' => 'shared/cart',
957 'context' => [ 'cart' ],
958 ],
959 ]
960 );
961 }
962
963 /**
964 * Get enabled modules. Filterable.
965 *
966 * @return array
967 */
968 protected function get_enabled_modules() {
969 $module_list = Fns::get_modules_list();
970 $enabled = [];
971
972 foreach ( $module_list as $slug => $info ) {
973 if ( defined( 'RTWPVG_VERSION' ) && 'variation_gallery' === $slug ) {
974 continue;
975 }
976 if ( defined( 'RTWPVS_VERSION' ) && 'variation_swatches' === $slug ) {
977 continue;
978 }
979 if ( ! empty( $info['active'] ) ) {
980 $enabled[] = str_replace( '_', '-', sanitize_key( $slug ) );
981 }
982 }
983
984 return $enabled;
985 }
986
987 /**
988 * Get enabled modules JS with context.
989 *
990 * @return array
991 */
992 protected function get_enabled_modules_js_with_context() {
993 $module_list = Fns::get_modules_list();
994 $modules = [];
995 $context = apply_filters(
996 'rtsb/optimizer/modules/js_context',
997 [
998 'checkout_fields_editor' => [ 'checkout' ],
999 'shopify_checkout' => [ 'checkout' ],
1000 ]
1001 );
1002
1003 foreach ( $module_list as $slug => $info ) {
1004 if ( defined( 'RTWPVG_VERSION' ) && 'variation_gallery' === $slug ) {
1005 continue;
1006 }
1007 if ( defined( 'RTWPVS_VERSION' ) && 'variation_swatches' === $slug ) {
1008 continue;
1009 }
1010 $clean_slug = str_replace( '_', '-', sanitize_key( $slug ) );
1011 $is_enabled = isset( $info['active'] ) && 'on' === $info['active'];
1012
1013 $modules[ $clean_slug ] = [
1014 'enabled' => $is_enabled,
1015 'category' => ! empty( $context[ $slug ] ) ? $context[ $slug ] : [ 'global' ],
1016 ];
1017 }
1018
1019 return $modules;
1020 }
1021
1022 /**
1023 * Get enabled modules CSS with context.
1024 *
1025 * @return array
1026 */
1027 protected function get_enabled_modules_css_with_context() {
1028 $module_list = Fns::get_modules_list();
1029 $modules = [];
1030 $context = apply_filters(
1031 'rtsb/optimizer/modules/css_context',
1032 [
1033 'checkout_fields_editor' => [ 'checkout' ],
1034 'shopify_checkout' => [ 'checkout' ],
1035 ]
1036 );
1037
1038 foreach ( $module_list as $slug => $info ) {
1039 if ( defined( 'RTWPVG_VERSION' ) && 'variation_gallery' === $slug ) {
1040 continue;
1041 }
1042 if ( defined( 'RTWPVS_VERSION' ) && 'variation_swatches' === $slug ) {
1043 continue;
1044 }
1045 $clean_slug = str_replace( '_', '-', sanitize_key( $slug ) );
1046 $is_enabled = isset( $info['active'] ) && 'on' === $info['active'];
1047
1048 $modules[ $clean_slug ] = [
1049 'enabled' => $is_enabled,
1050 'category' => ! empty( $context[ $slug ] ) ? $context[ $slug ] : [ 'global' ],
1051 ];
1052 }
1053
1054 return $modules;
1055 }
1056
1057 /**
1058 * Get enabled modules.
1059 *
1060 * @return array
1061 */
1062 protected function get_enabled_el_widgets() {
1063 $widget_data = Fns::get_widgets_list();
1064 $enabled = [];
1065
1066 foreach ( $widget_data as $slug => $info ) {
1067 if ( isset( $info['active'] ) && 'on' === $info['active'] ) {
1068 $enabled[] = str_replace( '_', '-', sanitize_key( $slug ) );
1069 }
1070 }
1071
1072 return $enabled;
1073 }
1074
1075 /**
1076 * Get enabled widgets with categories.
1077 *
1078 * @return array
1079 */
1080 protected function get_enabled_widgets_with_context() {
1081 $widget_list = Fns::get_widgets_list();
1082 $widgets = [];
1083
1084 foreach ( $widget_list as $slug => $info ) {
1085 $clean_slug = str_replace( '_', '-', sanitize_key( $slug ) );
1086 $is_enabled = isset( $info['active'] ) && 'on' === $info['active'];
1087
1088 $widgets[ $clean_slug ] = [
1089 'enabled' => $is_enabled,
1090 'category' => ! empty( $widget_list[ $slug ]['category'] ) ? $widget_list[ $slug ]['category'] : 'global',
1091 ];
1092 }
1093
1094 return $widgets;
1095 }
1096
1097 /**
1098 * Populate JS/CSS file arrays based on active modules.
1099 *
1100 * @return void
1101 */
1102 protected function collect_assets() {
1103 static $ran = false;
1104
1105 if ( $ran ) {
1106 return;
1107 }
1108
1109 $ran = true;
1110
1111 $this->js_files = $this->contextual_loading ? $this->get_scripts_by_context() : array_unique( $this->get_scripts() );
1112 $this->css_files = $this->contextual_loading ? $this->get_styles_by_context() : array_unique( $this->get_styles() );
1113 }
1114
1115 /**
1116 * Build and return the final bundled and minified CSS and JS.
1117 *
1118 * @return array
1119 */
1120 public function get_bundled_assets() {
1121 if ( null !== $this->bundled_assets ) {
1122 return $this->bundled_assets;
1123 }
1124
1125 $this->collect_assets();
1126
1127 $this->bundled_assets = [
1128 'js' => $this->contextual_loading ? $this->get_bundled_js_by_context() : $this->get_bundled_js(),
1129 'css' => $this->contextual_loading ? $this->get_bundled_css_by_context() : $this->get_bundled_css(),
1130 ];
1131
1132 return $this->bundled_assets;
1133 }
1134
1135 /**
1136 * Load optimized assets and return them by type
1137 *
1138 * @param string $type 'css' or 'js'.
1139 *
1140 * @return array
1141 */
1142 public function load_optimized_assets( $type = 'css' ) {
1143 if ( isset( self::$loaded_types[ $type ] ) ) {
1144 return [];
1145 }
1146
1147 $assets = [];
1148 $bundled = $this->get_bundled_assets();
1149 $context = Fns::detect_context();
1150
1151 if ( $this->contextual_loading ) {
1152 if ( 'js' === $type && ! empty( $bundled['js'] ) ) {
1153 $ctx = isset( $bundled['js'][ $context ] ) ? $context : 'global';
1154 if ( $ctx ) {
1155 $assets = (array) $bundled['js'][ $ctx ];
1156 }
1157 }
1158 if ( 'css' === $type && ! empty( $bundled['css'] ) ) {
1159 $ctx = isset( $bundled['css'][ $context ] ) ? $context : 'global';
1160 if ( $ctx ) {
1161 $assets = (array) $bundled['css'][ $ctx ];
1162 }
1163 }
1164 } else {
1165 if ( 'js' === $type && ! empty( $bundled['js'] ) ) {
1166 $assets = (array) $this->bundled_assets['js'];
1167 }
1168
1169 if ( 'css' === $type && ! empty( $bundled['css'] ) ) {
1170 $assets = (array) $this->bundled_assets['css'];
1171 }
1172 }
1173
1174 self::$loaded_types[ $type ] = true;
1175
1176 return $assets;
1177 }
1178
1179 /**
1180 * Get bundled JS, with caching to prevent multiple builds.
1181 *
1182 * @return array|null
1183 */
1184 protected function get_bundled_js() {
1185 if ( null !== $this->bundled_js ) {
1186 return $this->bundled_js;
1187 }
1188
1189 if ( empty( $this->js_files ) ) {
1190 return null;
1191 }
1192
1193 $handle = 'rtsb-bundled';
1194 $bundler = new AssetBundler( $handle, $this->js_files, 'js' );
1195 $src = $bundler->build();
1196 $deps = [ 'jquery', 'rtsb-tipsy' ];
1197
1198 $builder_page_id = BuilderFns::builder_page_id_by_page();
1199
1200 if ( Fns::is_elementor_page( $builder_page_id ) ) {
1201 $deps[] = 'imagesloaded';
1202 }
1203
1204 $this->bundled_js = [
1205 'handle' => $handle,
1206 'src' => $src,
1207 'deps' => apply_filters( 'rtsb/optimizer/scripts/deps', $deps ),
1208 'footer' => true,
1209 ];
1210
1211 return $this->bundled_js;
1212 }
1213
1214 /**
1215 * Get bundled CSS, with caching to prevent multiple builds.
1216 *
1217 * @return array|null
1218 */
1219 protected function get_bundled_css() {
1220 if ( null !== $this->bundled_css ) {
1221 return $this->bundled_css;
1222 }
1223
1224 if ( empty( $this->css_files ) ) {
1225 return null;
1226 }
1227
1228 $bundler = new AssetBundler( 'rtsb-bundled', $this->css_files, 'css' );
1229 $src = $bundler->build();
1230
1231 $this->bundled_css = [
1232 'handle' => 'rtsb-bundled',
1233 'src' => $src,
1234 ];
1235
1236 return $this->bundled_css;
1237 }
1238
1239 /**
1240 * Get bundled CSS by context
1241 *
1242 * @return array|null
1243 */
1244 public function get_bundled_css_by_context() {
1245 $css_files_by_context = $this->get_contextual_assets( 'css' );
1246 $bundled_contexts = [];
1247
1248 foreach ( $css_files_by_context as $context => $files ) {
1249 if ( empty( $files ) ) {
1250 continue;
1251 }
1252
1253 $bundler = new AssetBundler( "rtsb-bundled-$context", $files, 'css' );
1254 $src = $bundler->build();
1255
1256 $bundled_contexts[ $context ] = [
1257 'handle' => "rtsb-bundled-$context",
1258 'src' => $src,
1259 ];
1260 }
1261
1262 return $bundled_contexts;
1263 }
1264
1265 /**
1266 * Forcefully regenerate bundled JS and CSS assets.
1267 *
1268 * @return array
1269 */
1270 public function regenerate_bundles() {
1271 $this->bundled_js = null;
1272 $this->bundled_css = null;
1273 $this->js_files = [];
1274 $this->css_files = [];
1275
1276 $this->collect_assets();
1277
1278 return [
1279 'js' => $this->contextual_loading ? $this->get_bundled_js_by_context() : $this->get_bundled_js(),
1280 'css' => $this->contextual_loading ? $this->get_bundled_css_by_context() : $this->get_bundled_css(),
1281 ];
1282 }
1283
1284 /**
1285 * Get context.
1286 *
1287 * @return object
1288 */
1289 private function context() {
1290 return ( function_exists( 'rtsbpro' ) && rtsb()->has_pro() ) ? rtsbpro() : rtsb();
1291 }
1292
1293 /**
1294 * Get shared style mappings for modules and widgets.
1295 *
1296 * @return array
1297 */
1298 private function get_shared_style_components() {
1299 return apply_filters(
1300 'rtsb/optimizer/styles/shared/components',
1301 [
1302 'checkout' => [
1303 'file_name' => 'shared/checkout',
1304 'modules' => [ 'quick-checkout', 'shopify-checkout' ],
1305 'widgets' => [
1306 'billing-form',
1307 'shipping-form',
1308 'order-notes',
1309 'order-review',
1310 'checkout-payment',
1311 'coupon-form',
1312 'checkout-login-form',
1313 'shipping-method',
1314 ],
1315 'context' => [ 'global' ],
1316 ],
1317 'slider' => [
1318 'file_name' => 'shared/slider-core',
1319 'modules' => [ 'variation-gallery' ],
1320 'widgets' => [
1321 'products-slider',
1322 'team-member',
1323 'upsells-products-slider-custom',
1324 'related-products-slider-custom',
1325 'cross-sells-custom-slider',
1326 'hero-slider',
1327 ],
1328 'context' => [ 'global' ],
1329 ],
1330 ]
1331 );
1332 }
1333 }
1334