PluginProbe
ShopBuilder – WooCommerce Builder For Elementor / 3.1.2
ShopBuilder – WooCommerce Builder For Elementor v3.1.2
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.2, at app/Controllers/AssetRegistry.php

1,320 lines 30.7 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 ( ! empty( $info['active'] ) ) {
975 $enabled[] = str_replace( '_', '-', sanitize_key( $slug ) );
976 }
977 }
978
979 return $enabled;
980 }
981
982 /**
983 * Get enabled modules JS with context.
984 *
985 * @return array
986 */
987 protected function get_enabled_modules_js_with_context() {
988 $module_list = Fns::get_modules_list();
989 $modules = [];
990 $context = apply_filters(
991 'rtsb/optimizer/modules/js_context',
992 [
993 'checkout_fields_editor' => [ 'checkout' ],
994 'shopify_checkout' => [ 'checkout' ],
995 ]
996 );
997
998 foreach ( $module_list as $slug => $info ) {
999 $clean_slug = str_replace( '_', '-', sanitize_key( $slug ) );
1000 $is_enabled = isset( $info['active'] ) && 'on' === $info['active'];
1001
1002 $modules[ $clean_slug ] = [
1003 'enabled' => $is_enabled,
1004 'category' => ! empty( $context[ $slug ] ) ? $context[ $slug ] : [ 'global' ],
1005 ];
1006 }
1007
1008 return $modules;
1009 }
1010
1011 /**
1012 * Get enabled modules CSS with context.
1013 *
1014 * @return array
1015 */
1016 protected function get_enabled_modules_css_with_context() {
1017 $module_list = Fns::get_modules_list();
1018 $modules = [];
1019 $context = apply_filters(
1020 'rtsb/optimizer/modules/css_context',
1021 [
1022 'checkout_fields_editor' => [ 'checkout' ],
1023 'shopify_checkout' => [ 'checkout' ],
1024 ]
1025 );
1026
1027 foreach ( $module_list as $slug => $info ) {
1028 $clean_slug = str_replace( '_', '-', sanitize_key( $slug ) );
1029 $is_enabled = isset( $info['active'] ) && 'on' === $info['active'];
1030
1031 $modules[ $clean_slug ] = [
1032 'enabled' => $is_enabled,
1033 'category' => ! empty( $context[ $slug ] ) ? $context[ $slug ] : [ 'global' ],
1034 ];
1035 }
1036
1037 return $modules;
1038 }
1039
1040 /**
1041 * Get enabled modules.
1042 *
1043 * @return array
1044 */
1045 protected function get_enabled_el_widgets() {
1046 $widget_data = Fns::get_widgets_list();
1047 $enabled = [];
1048
1049 foreach ( $widget_data as $slug => $info ) {
1050 if ( isset( $info['active'] ) && 'on' === $info['active'] ) {
1051 $enabled[] = str_replace( '_', '-', sanitize_key( $slug ) );
1052 }
1053 }
1054
1055 return $enabled;
1056 }
1057
1058 /**
1059 * Get enabled widgets with categories.
1060 *
1061 * @return array
1062 */
1063 protected function get_enabled_widgets_with_context() {
1064 $widget_list = Fns::get_widgets_list();
1065 $widgets = [];
1066
1067 foreach ( $widget_list as $slug => $info ) {
1068 $clean_slug = str_replace( '_', '-', sanitize_key( $slug ) );
1069 $is_enabled = isset( $info['active'] ) && 'on' === $info['active'];
1070
1071 $widgets[ $clean_slug ] = [
1072 'enabled' => $is_enabled,
1073 'category' => ! empty( $widget_list[ $slug ]['category'] ) ? $widget_list[ $slug ]['category'] : 'global',
1074 ];
1075 }
1076
1077 return $widgets;
1078 }
1079
1080 /**
1081 * Populate JS/CSS file arrays based on active modules.
1082 *
1083 * @return void
1084 */
1085 protected function collect_assets() {
1086 static $ran = false;
1087
1088 if ( $ran ) {
1089 return;
1090 }
1091
1092 $ran = true;
1093
1094 $this->js_files = $this->contextual_loading ? $this->get_scripts_by_context() : array_unique( $this->get_scripts() );
1095 $this->css_files = $this->contextual_loading ? $this->get_styles_by_context() : array_unique( $this->get_styles() );
1096 }
1097
1098 /**
1099 * Build and return the final bundled and minified CSS and JS.
1100 *
1101 * @return array
1102 */
1103 public function get_bundled_assets() {
1104 if ( null !== $this->bundled_assets ) {
1105 return $this->bundled_assets;
1106 }
1107
1108 $this->collect_assets();
1109
1110 $this->bundled_assets = [
1111 'js' => $this->contextual_loading ? $this->get_bundled_js_by_context() : $this->get_bundled_js(),
1112 'css' => $this->contextual_loading ? $this->get_bundled_css_by_context() : $this->get_bundled_css(),
1113 ];
1114
1115 return $this->bundled_assets;
1116 }
1117
1118 /**
1119 * Load optimized assets and return them by type
1120 *
1121 * @param string $type 'css' or 'js'.
1122 *
1123 * @return array
1124 */
1125 public function load_optimized_assets( $type = 'css' ) {
1126 if ( isset( self::$loaded_types[ $type ] ) ) {
1127 return [];
1128 }
1129
1130 $assets = [];
1131 $bundled = $this->get_bundled_assets();
1132 $context = Fns::detect_context();
1133
1134 if ( $this->contextual_loading ) {
1135 if ( 'js' === $type && ! empty( $bundled['js'] ) ) {
1136 $ctx = isset( $bundled['js'][ $context ] ) ? $context : 'global';
1137
1138 if ( $ctx ) {
1139 $assets = (array) $bundled['js'][ $ctx ];
1140 }
1141 }
1142
1143 if ( 'css' === $type && ! empty( $bundled['css'] ) ) {
1144 $ctx = isset( $bundled['css'][ $context ] ) ? $context : 'global';
1145
1146 if ( $ctx ) {
1147 $assets = (array) $bundled['css'][ $ctx ];
1148 }
1149 }
1150 } else {
1151 if ( 'js' === $type && ! empty( $bundled['js'] ) ) {
1152 $assets = (array) $this->bundled_assets['js'];
1153 }
1154
1155 if ( 'css' === $type && ! empty( $bundled['css'] ) ) {
1156 $assets = (array) $this->bundled_assets['css'];
1157 }
1158 }
1159
1160 self::$loaded_types[ $type ] = true;
1161
1162 return $assets;
1163 }
1164
1165 /**
1166 * Get bundled JS, with caching to prevent multiple builds.
1167 *
1168 * @return array|null
1169 */
1170 protected function get_bundled_js() {
1171 if ( null !== $this->bundled_js ) {
1172 return $this->bundled_js;
1173 }
1174
1175 if ( empty( $this->js_files ) ) {
1176 return null;
1177 }
1178
1179 $handle = 'rtsb-bundled';
1180 $bundler = new AssetBundler( $handle, $this->js_files, 'js' );
1181 $src = $bundler->build();
1182 $deps = [ 'jquery', 'rtsb-tipsy' ];
1183
1184 $builder_page_id = BuilderFns::builder_page_id_by_page();
1185
1186 if ( Fns::is_elementor_page( $builder_page_id ) ) {
1187 $deps[] = 'imagesloaded';
1188 }
1189
1190 $this->bundled_js = [
1191 'handle' => $handle,
1192 'src' => $src,
1193 'deps' => apply_filters( 'rtsb/optimizer/scripts/deps', $deps ),
1194 'footer' => true,
1195 ];
1196
1197 return $this->bundled_js;
1198 }
1199
1200 /**
1201 * Get bundled CSS, with caching to prevent multiple builds.
1202 *
1203 * @return array|null
1204 */
1205 protected function get_bundled_css() {
1206 if ( null !== $this->bundled_css ) {
1207 return $this->bundled_css;
1208 }
1209
1210 if ( empty( $this->css_files ) ) {
1211 return null;
1212 }
1213
1214 $bundler = new AssetBundler( 'rtsb-bundled', $this->css_files, 'css' );
1215 $src = $bundler->build();
1216
1217 $this->bundled_css = [
1218 'handle' => 'rtsb-bundled',
1219 'src' => $src,
1220 ];
1221
1222 return $this->bundled_css;
1223 }
1224
1225 /**
1226 * Get bundled CSS by context
1227 *
1228 * @return array|null
1229 */
1230 public function get_bundled_css_by_context() {
1231 $css_files_by_context = $this->get_contextual_assets( 'css' );
1232 $bundled_contexts = [];
1233
1234 foreach ( $css_files_by_context as $context => $files ) {
1235 if ( empty( $files ) ) {
1236 continue;
1237 }
1238
1239 $bundler = new AssetBundler( "rtsb-bundled-$context", $files, 'css' );
1240 $src = $bundler->build();
1241
1242 $bundled_contexts[ $context ] = [
1243 'handle' => "rtsb-bundled-$context",
1244 'src' => $src,
1245 ];
1246 }
1247
1248 return $bundled_contexts;
1249 }
1250
1251 /**
1252 * Forcefully regenerate bundled JS and CSS assets.
1253 *
1254 * @return array
1255 */
1256 public function regenerate_bundles() {
1257 $this->bundled_js = null;
1258 $this->bundled_css = null;
1259 $this->js_files = [];
1260 $this->css_files = [];
1261
1262 $this->collect_assets();
1263
1264 return [
1265 'js' => $this->contextual_loading ? $this->get_bundled_js_by_context() : $this->get_bundled_js(),
1266 'css' => $this->contextual_loading ? $this->get_bundled_css_by_context() : $this->get_bundled_css(),
1267 ];
1268 }
1269
1270 /**
1271 * Get context.
1272 *
1273 * @return object
1274 */
1275 private function context() {
1276 return ( function_exists( 'rtsbpro' ) && rtsb()->has_pro() ) ? rtsbpro() : rtsb();
1277 }
1278
1279 /**
1280 * Get shared style mappings for modules and widgets.
1281 *
1282 * @return array
1283 */
1284 private function get_shared_style_components() {
1285 return apply_filters(
1286 'rtsb/optimizer/styles/shared/components',
1287 [
1288 'checkout' => [
1289 'file_name' => 'shared/checkout',
1290 'modules' => [ 'quick-checkout', 'shopify-checkout' ],
1291 'widgets' => [
1292 'billing-form',
1293 'shipping-form',
1294 'order-notes',
1295 'order-review',
1296 'checkout-payment',
1297 'coupon-form',
1298 'checkout-login-form',
1299 'shipping-method',
1300 ],
1301 'context' => [ 'global' ],
1302 ],
1303 'slider' => [
1304 'file_name' => 'shared/slider-core',
1305 'modules' => [ 'variation-gallery' ],
1306 'widgets' => [
1307 'products-slider',
1308 'team-member',
1309 'upsells-products-slider-custom',
1310 'related-products-slider-custom',
1311 'cross-sells-custom-slider',
1312 'hero-slider',
1313 ],
1314 'context' => [ 'global' ],
1315 ],
1316 ]
1317 );
1318 }
1319 }
1320