PluginProbe
Master Addons for Elementor – Elementor Addons, Widgets, Mega Menu Builder, Popup Builder, Widget Builder & Template Kits / 3.1.2
Master Addons for Elementor – Elementor Addons, Widgets, Mega Menu Builder, Popup Builder, Widget Builder & Template Kits v3.1.2
3.2.2 3.2.3 3.2.1 3.2.0 3.1.9 3.1.8 3.1.7 3.1.6 3.1.5 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.9 trunk 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.1.3 1.1.4 1.1.5 All 174 releases
master-addons / inc / classes / assets-manager.php

assets-manager.php in Master Addons for Elementor – Elementor Addons, Widgets, Mega Menu Builder, Popup Builder, Widget Builder & Template Kits 3.1.2, at inc/classes/assets-manager.php

980 lines 32.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Master Addons - Assets Manager
5 *
6 * Central registry for all vendor libraries and asset registration.
7 * Handles dependency chains and provides organized asset management.
8 *
9 * @package MasterAddons\Inc\Classes
10 * @since 2.1.0
11 */
12
13 namespace MasterAddons\Inc\Classes;
14
15 if (!defined('ABSPATH')) {
16 exit;
17 }
18
19 class Assets_Manager
20 {
21 /**
22 * Singleton instance
23 */
24 private static $instance = null;
25
26 /**
27 * Vendor registry cache
28 */
29 private static $registry = null;
30
31 /**
32 * Get singleton instance
33 */
34 public static function get_instance()
35 {
36 if (null === self::$instance) {
37 self::$instance = new self();
38 }
39 return self::$instance;
40 }
41
42 /**
43 * Constructor
44 */
45 public function __construct()
46 {
47 // Register vendor assets early - both frontend and admin
48 add_action('wp_enqueue_scripts', [$this, 'register_all'], 5);
49 add_action('admin_enqueue_scripts', [$this, 'register_all'], 5);
50
51 // Also register in Elementor editor
52 add_action('elementor/editor/after_enqueue_scripts', [$this, 'register_all'], 5);
53 add_action('elementor/preview/enqueue_scripts', [$this, 'register_all'], 5);
54 }
55
56 /**
57 * Get vendor registry
58 *
59 * All vendor libraries with their file paths and dependencies.
60 * No 'name' key needed - the array key IS the identifier.
61 *
62 * @return array
63 */
64 public static function get_registry()
65 {
66 if (self::$registry !== null) {
67 return self::$registry;
68 }
69
70 self::$registry = [
71 // =========================================================
72 // TOOLTIP & POPOVER
73 // =========================================================
74 'popper' => [
75 'files' => [
76 'js' => 'popper.min.js',
77 ],
78 'deps' => [],
79 ],
80
81 'tippy' => [
82 'files' => [
83 'css' => 'tippyjs/css/tippy.css',
84 'js' => 'tippyjs/js/tippy.min.js',
85 ],
86 'deps' => ['popper'],
87 ],
88
89 // =========================================================
90 // LIGHTBOX & GALLERY
91 // =========================================================
92 'fancybox' => [
93 'files' => [
94 'css' => 'fancybox/jquery.fancybox.min.css',
95 'js' => 'fancybox/jquery.fancybox.min.js',
96 ],
97 'deps' => [],
98 ],
99
100 'gridder' => [
101 'files' => [
102 'css' => 'gridder/css/jquery.gridder.min.css',
103 'js' => 'gridder/js/jquery.gridder.min.js',
104 ],
105 'deps' => [],
106 ],
107
108 // =========================================================
109 // GRID & FILTERING
110 // =========================================================
111 'isotope' => [
112 'files' => [
113 'js' => 'isotope/isotope.js',
114 ],
115 'deps' => [],
116 ],
117
118 'macy' => [
119 'files' => [
120 'js' => 'macy/macy.js',
121 ],
122 'deps' => [],
123 ],
124
125 // =========================================================
126 // CAROUSEL & SLIDER
127 // =========================================================
128 'swiper' => [
129 'files' => [
130 'css' => 'swiper/css/swiper.min.css',
131 'js' => 'swiper/js/swiper-bundle.min.js',
132 ],
133 'deps' => [],
134 ],
135
136 'swiper-carousel' => [
137 'files' => [
138 'css' => 'common/swiper-carousel.min.css',
139 ],
140 'path' => 'css', // Override: assets/css/
141 'deps' => ['swiper'],
142 ],
143
144 'owl-carousel' => [
145 'files' => [
146 'css' => 'owlcarousel/owl.carousel.min.css',
147 'js' => 'owlcarousel/owl.carousel.min.js',
148 ],
149 'deps' => [],
150 'deprecated' => true,
151 ],
152
153 // =========================================================
154 // IMAGE EFFECTS
155 // =========================================================
156 'tilt' => [
157 'files' => [
158 'js' => 'tilt/tilt.jquery.min.js',
159 ],
160 'deps' => [],
161 ],
162
163 'twentytwenty' => [
164 'files' => [
165 'css' => 'twentytwenty/css/twentytwenty.css',
166 'js' => 'twentytwenty/js/jquery.twentytwenty.js',
167 ],
168 'deps' => ['jquery-event-move'],
169 ],
170
171 'jquery-event-move' => [
172 'files' => [
173 'js' => 'twentytwenty/js/jquery.event.move.js',
174 ],
175 'deps' => [],
176 ],
177
178 // =========================================================
179 // ANIMATION
180 // =========================================================
181 // Note: anime.js is now bundled via ES import in modules that need it
182 // reveal is now integrated into the reveal module with anime.js v4
183
184 'rellax' => [
185 'files' => [
186 'js' => 'rellax/rellax.min.js',
187 ],
188 'deps' => [],
189 ],
190
191 // =========================================================
192 // BACKGROUND & EFFECTS
193 // =========================================================
194 'vegas' => [
195 'files' => [
196 'css' => 'vegas/vegas.min.css',
197 'js' => 'vegas/vegas.min.js',
198 ],
199 'deps' => ['common'],
200 ],
201
202 'particles' => [
203 'files' => [
204 'js' => 'particles/particles.js',
205 ],
206 'deps' => [],
207 ],
208
209 // =========================================================
210 // COUNTDOWN & TIMER
211 // =========================================================
212 'countdown' => [
213 'files' => [
214 'js' => 'countdown/jquery.countdown.js',
215 ],
216 'deps' => [],
217 ],
218
219 // =========================================================
220 // PROGRESS & LOADING
221 // =========================================================
222 'loading-bar' => [
223 'files' => [
224 'js' => 'loading-bar/loading-bar.js',
225 ],
226 'deps' => [],
227 ],
228
229 // =========================================================
230 // SCROLLING
231 // =========================================================
232 'perfect-scrollbar' => [
233 'files' => [
234 'css' => 'perfect-scrollbar/perfect-scrollbar.min.css',
235 'js' => 'perfect-scrollbar/perfect-scrollbar.min.js',
236 ],
237 'deps' => [],
238 ],
239
240 // =========================================================
241 // LAZY LOADING
242 // =========================================================
243 'lazysizes' => [
244 'files' => [
245 'js' => 'lazysizes/lazysizes.min.js',
246 ],
247 'deps' => [],
248 ],
249
250 // =========================================================
251 // NEWS TICKER
252 // =========================================================
253 'jquery-rss' => [
254 'files' => [
255 'js' => 'newsticker/js/jquery.rss.min.js',
256 ],
257 'deps' => [],
258 ],
259
260 // =========================================================
261 // TABLE OF CONTENTS
262 // =========================================================
263 'table-of-content' => [
264 'files' => [
265 'js' => 'jltma-table-of-content/jltma-table-of-content.js',
266 ],
267 'deps' => [],
268 ],
269
270 // =========================================================
271 // TOGGLE CONTENT
272 // =========================================================
273 'toggle-content' => [
274 'files' => [
275 'js' => 'toggle-content/toggle-content.js',
276 ],
277 'deps' => [],
278 ],
279
280 // =========================================================
281 // ICON FONTS
282 // =========================================================
283 'elementor-icons' => [
284 'files' => [
285 'css' => 'elementor-icons.css',
286 ],
287 'path' => 'fonts/elementor-icon',
288 'deps' => [],
289 ],
290
291 // =========================================================
292 // COMMON/SHARED STYLES
293 // =========================================================
294 'common' => [
295 'files' => [
296 'css' => 'common/common.min.css',
297 ],
298 'path' => 'css',
299 'deps' => [],
300 ],
301
302 // =========================================================
303 // Animation Effects
304 // =========================================================
305 'animation' => [
306 'files' => [
307 'css' => 'common/animations.css',
308 ],
309 'path' => 'css',
310 'deps' => [],
311 ],
312
313 // =========================================================
314 // FORM & UI LIBRARIES
315 // =========================================================
316 'select2' => [
317 'files' => [
318 'css' => 'select2/select2.min.css',
319 'js' => 'select2/select2.min.js',
320 ],
321 'deps' => [],
322 ],
323
324 // =========================================================
325 // RECOMMENDED PLUGINS
326 // =========================================================
327 'recommended-plugins' => [
328 'files' => [
329 'css' => 'css/admin/recommended-plugins.css',
330 'js' => 'js/admin/recommended-plugins.js',
331 ],
332 'path' => '', // Root assets folder
333 'deps' => ['jquery'],
334 ],
335
336 // =========================================================
337 // ADMIN SDK
338 // =========================================================
339 'admin-sdk' => [
340 'files' => [
341 'css' => 'css/admin/master-addons-admin-sdk.css',
342 'js' => 'js/admin/master-addons-admin-sdk.js',
343 ],
344 'path' => '', // Root assets folder
345 'deps' => ['jquery'],
346 ],
347
348 // =========================================================
349 // ADMIN SETTINGS ASSETS
350 // =========================================================
351 'admin-settings' => [
352 'files' => [
353 'css' => 'css/admin/admin-settings.css',
354 'js' => 'js/admin/admin-settings.js',
355 ],
356 'path' => '', // Root assets folder
357 'deps' => ['react', 'react-dom', 'wp-element', 'wp-i18n', 'wp-api-fetch'],
358 ],
359
360 // =========================================================
361 // SETUP WIZARD ASSETS
362 // =========================================================
363 'setup-wizard' => [
364 'files' => [
365 'css' => 'css/admin/setup-wizard.css',
366 'js' => 'js/admin/setup-wizard.js',
367 ],
368 'path' => '', // Root assets folder
369 'deps' => ['react', 'react-dom', 'wp-element', 'wp-i18n', 'wp-api-fetch'],
370 ],
371
372 // =========================================================
373 // THEME BUILDER ASSETS
374 // =========================================================
375 'theme-builder' => [
376 'files' => [
377 'css' => 'css/admin/theme-builder.css',
378 'js' => 'js/admin/theme-builder.js',
379 ],
380 'path' => '', // Root assets folder
381 'deps' => ['select2'],
382 ],
383
384 // =========================================================
385 // COMMENTS BUILDER
386 // =========================================================
387 'comments' => [
388 'files' => [
389 'css' => 'css/theme-builder-comments.css',
390 'js' => 'js/theme-builder-comments.js',
391 ],
392 'path' => '', // Root assets folder
393 'deps' => [],
394 ],
395
396 // =========================================================
397 // POPUP BUILDER ASSETS
398 // =========================================================
399 'popup-builder-admin' => [
400 'files' => [
401 'css' => 'css/admin/popup-builder/popup-builder.css',
402 'js' => 'js/admin/popup-builder/popup-admin.js',
403 ],
404 'path' => '',
405 'deps' => ['select2'],
406 ],
407 'popup-builder-frontend' => [
408 'files' => [
409 'css' => 'css/admin/popup-builder/popup-frontend.css',
410 'js' => 'js/admin/popup-builder/modal-popup.js',
411 ],
412 'path' => '',
413 'deps' => ['animation'],
414 ],
415 'popup-builder-elementor' => [
416 'files' => [
417 'css' => 'css/admin/popup-builder/elementor-editor.css',
418 'js' => 'js/admin/popup-builder/elementor-editor.js',
419 ],
420 'path' => '',
421 'deps' => [],
422 ],
423
424 // =========================================================
425 // TEMPLATE LIBRARY & PAGE IMPORTER
426 // =========================================================
427 'template-library' => [
428 'files' => [
429 'css' => 'css/admin/template-library.css',
430 'js' => 'js/admin/template-library.js',
431 ],
432 'path' => '', // Root assets folder
433 'wp_deps' => ['wp-element', 'wp-i18n', 'wp-components', 'wp-api-fetch', 'wp-hooks', 'wp-util'],
434 'wp_css_deps' => ['wp-components'],
435 'deps' => [],
436 ],
437
438 'template-kits-app' => [
439 'files' => [
440 'js' => 'js/admin/template-kits-app.js',
441 'css' => 'css/admin/template-kits-app.css',
442 ],
443 'path' => '', // Root assets folder
444 'wp_deps' => ['wp-element', 'wp-i18n'],
445 'deps' => [],
446 ],
447
448 // =========================================================
449 // Wizard Builder
450 // =========================================================
451 'widget-builder' => [
452 'files' => [
453 'css' => 'css/admin/widget-builder.css',
454 ],
455 'path' => '',
456 'deps' => [],
457 ],
458
459 'widget-admin' => [
460 'files' => [
461 'js' => 'js/admin/widget-admin.js',
462 ],
463 'path' => '',
464 'deps' => [],
465 ],
466
467 'widget-builder-app' => [
468 'files' => [
469 'css' => 'css/admin/widget-builder-app.css',
470 'js' => 'js/admin/widget-builder-app.js',
471 ],
472 'path' => '',
473 'deps' => [],
474 ],
475
476 'widget-builder-tooltip' => [
477 'files' => [
478 'js' => 'js/admin/widget-builder-tooltip.js',
479 ],
480 'path' => '',
481 'deps' => ['widget-builder-app'],
482 ],
483
484 'page-importer' => [
485 'files' => [
486 'css' => 'css/admin/page-importer.css',
487 'js' => 'js/admin/page-importer.js',
488 ],
489 'path' => '', // Root assets folder
490 'deps' => ['template-library', 'widget-builder'],
491 ],
492
493 // =========================================================
494 // IMAGE OPTIMIZER (Free Version - Upsell Display)
495 // =========================================================
496 'image-optimizer-base' => [
497 'files' => [
498 'js' => 'js/admin/image-optimizer/image-optimizer-base.js',
499 ],
500 'path' => '', // Root assets folder
501 'deps' => [],
502 ],
503
504 // =========================================================
505 // ELEMENTOR EDITOR CONTROLS
506 // =========================================================
507 'module-editor' => [
508 'files' => [
509 'js' => 'js/admin/module-editor.js',
510 ],
511 'path' => '',
512 'deps' => [],
513 ],
514
515 'visual-select' => [
516 'files' => [
517 'css' => 'css/admin/master-addons-editor.css',
518 'js' => 'js/admin/visual-select.js',
519 ],
520 'path' => '',
521 'deps' => ['module-editor'],
522 ],
523
524 'file-select-control' => [
525 'files' => [
526 'js' => 'js/admin/file-select-control.js',
527 ],
528 'path' => '',
529 'deps' => [],
530 ],
531
532 // =========================================================
533 // EXTERNAL CDN LIBRARIES
534 // =========================================================
535 'google-recaptcha' => [
536 'files' => [
537 'js' => 'https://www.google.com/recaptcha/api.js',
538 ],
539 'deps' => [],
540 'cdn' => true,
541 ],
542 ];
543
544 // Allow extensions to add more vendors
545 self::$registry = apply_filters('jltma/assets/vendor_registry', self::$registry);
546
547 return self::$registry;
548 }
549
550 /**
551 * Get single vendor config
552 *
553 * @param string $handle Vendor handle
554 * @return array|null
555 */
556 public static function get($handle)
557 {
558 $registry = self::get_registry();
559 return $registry[$handle] ?? null;
560 }
561
562 /**
563 * Check if vendor exists
564 *
565 * @param string $handle Vendor handle
566 * @return bool
567 */
568 public static function exists($handle)
569 {
570 $registry = self::get_registry();
571 return isset($registry[$handle]);
572 }
573
574 /**
575 * Get all dependencies for a vendor (recursive)
576 *
577 * @param string $handle Vendor handle
578 * @return array Flat array of all dependency handles
579 */
580 public static function get_all_deps($handle)
581 {
582 $registry = self::get_registry();
583 $all_deps = [];
584
585 if (!isset($registry[$handle])) {
586 return $all_deps;
587 }
588
589 foreach ($registry[$handle]['deps'] ?? [] as $dep) {
590 // Get nested deps first (depth-first)
591 $nested = self::get_all_deps($dep);
592 $all_deps = array_merge($all_deps, $nested);
593 // Then add the dep itself
594 $all_deps[] = $dep;
595 }
596
597 return array_unique($all_deps);
598 }
599
600 /**
601 * Topological sort for correct registration order
602 * Ensures dependencies are registered before dependents
603 *
604 * @return array Sorted vendor registry
605 */
606 public static function get_sorted_registry()
607 {
608 $registry = self::get_registry();
609 $sorted = [];
610 $visited = [];
611
612 foreach (array_keys($registry) as $handle) {
613 self::visit($handle, $registry, $sorted, $visited);
614 }
615
616 return $sorted;
617 }
618
619 /**
620 * Visit node for topological sort
621 */
622 private static function visit($handle, $registry, &$sorted, &$visited)
623 {
624 if (isset($visited[$handle])) {
625 return;
626 }
627
628 $visited[$handle] = true;
629
630 if (isset($registry[$handle]['deps'])) {
631 foreach ($registry[$handle]['deps'] as $dep) {
632 if (isset($registry[$dep])) {
633 self::visit($dep, $registry, $sorted, $visited);
634 }
635 }
636 }
637
638 $sorted[$handle] = $registry[$handle];
639 }
640
641 /**
642 * Register all vendor assets
643 * Called early in wp_enqueue_scripts
644 */
645 public function register_all()
646 {
647 $sorted = self::get_sorted_registry();
648 $assets_url = defined('JLTMA_ASSETS') ? JLTMA_ASSETS : (defined('JLTMA_PRO_ASSETS') ? JLTMA_PRO_ASSETS : '');
649 $vendor_url = $assets_url . 'vendor/';
650 $pro_vendor_url = defined('JLTMA_PRO_ASSETS') ? JLTMA_PRO_ASSETS . 'vendor/' : '';
651 $version = defined('JLTMA_VER') ? JLTMA_VER : (defined('JLTMA_PRO_VER') ? JLTMA_PRO_VER : '1.0.0');
652 $suffix = defined('JLTMA_SCRIPT_SUFFIX') ? JLTMA_SCRIPT_SUFFIX : '.min';
653
654 foreach ($sorted as $handle => $vendor) {
655 // Skip pro vendors if not licensed
656 if (!empty($vendor['pro']) && !Helper::jltma_premium()) {
657 continue;
658 }
659
660 $wp_handle = 'jltma-' . $handle;
661
662 // Determine base URL based on path override and pro status
663 $is_pro = !empty($vendor['pro']);
664 $pro_assets = defined('JLTMA_PRO_ASSETS') ? JLTMA_PRO_ASSETS : $assets_url;
665 $base_assets = $is_pro ? $pro_assets : $assets_url;
666
667 if (isset($vendor['path'])) {
668 // Custom path: use pro or free assets base + custom path
669 $base_url = $base_assets . ($vendor['path'] ? $vendor['path'] . '/' : '');
670 } elseif ($is_pro && $pro_vendor_url) {
671 // Pro vendor: use pro vendor folder
672 $base_url = $pro_vendor_url;
673 } else {
674 // Free vendor: use free vendor folder
675 $base_url = $vendor_url;
676 }
677
678 // Build dependency arrays for WordPress
679 $css_deps = $vendor['wp_css_deps'] ?? [];
680 $js_deps = array_merge(['jquery'], $vendor['wp_deps'] ?? []);
681
682 foreach ($vendor['deps'] ?? [] as $dep) {
683 $dep_handle = 'jltma-' . $dep;
684 if (isset($sorted[$dep]['files']['css'])) {
685 $css_deps[] = $dep_handle;
686 }
687 if (isset($sorted[$dep]['files']['js'])) {
688 $js_deps[] = $dep_handle;
689 }
690 }
691
692 // Apply .min suffix for local built assets (path === '')
693 $use_suffix = ($suffix && isset($vendor['path']) && $vendor['path'] === '' && empty($vendor['cdn']));
694
695 // Register CSS
696 if (!empty($vendor['files']['css'])) {
697 $css_file = $vendor['files']['css'];
698 if ($use_suffix) {
699 $css_file = preg_replace('/\.css$/', $suffix . '.css', $css_file);
700 }
701
702 // Check if it's a CDN/full URL or relative path
703 $css_url = $this->is_external_url($vendor['files']['css'])
704 ? $vendor['files']['css']
705 : $base_url . $css_file;
706
707 wp_register_style(
708 $wp_handle,
709 $css_url,
710 $css_deps,
711 !empty($vendor['cdn']) ? null : $this->resolve_asset_version($css_file, $vendor, $is_pro, $version)
712 );
713 }
714
715 // Register JS
716 if (!empty($vendor['files']['js'])) {
717 $js_file = $vendor['files']['js'];
718 if ($use_suffix) {
719 $js_file = preg_replace('/\.js$/', $suffix . '.js', $js_file);
720 }
721
722 // Check if it's a CDN/full URL or relative path
723 $js_url = $this->is_external_url($vendor['files']['js'])
724 ? $vendor['files']['js']
725 : $base_url . $js_file;
726
727 // Load in footer by default (true), unless in_header is explicitly true
728 $in_footer = isset($vendor['in_header']) && $vendor['in_header'] === true ? false : true;
729
730 wp_register_script(
731 $wp_handle,
732 $js_url,
733 $js_deps,
734 !empty($vendor['cdn']) ? null : $this->resolve_asset_version($js_file, $vendor, $is_pro, $version),
735 $in_footer
736 );
737 }
738 }
739
740 // Register Font Awesome with global handle (if not already registered by Elementor/other plugins)
741 $this->register_global_assets();
742
743 // Allow extensions to register additional assets
744 do_action('jltma/assets/after_register_vendors');
745 }
746
747 /**
748 * Resolve the version string for an asset.
749 *
750 * Local, built assets (path override of '', non-CDN, non-external) are versioned
751 * by their file modification time so a rebuild or plugin update busts the browser
752 * cache automatically. Everything else falls back to the plugin version.
753 *
754 * @param string $relative_file Asset path relative to its assets base (already suffixed).
755 * @param array $vendor Registry entry for the asset.
756 * @param bool $is_pro Whether the asset lives in the premium assets folder.
757 * @param string $fallback Version to use when mtime is unavailable.
758 * @return string
759 */
760 private function resolve_asset_version($relative_file, $vendor, $is_pro, $fallback)
761 {
762 $is_local = isset($vendor['path'])
763 && $vendor['path'] === ''
764 && empty($vendor['cdn'])
765 && !$this->is_external_url($relative_file);
766
767 if (!$is_local) {
768 return $fallback;
769 }
770
771 $base_dir = $is_pro
772 ? (defined('JLTMA_PRO_PATH') ? trailingslashit(JLTMA_PRO_PATH) . 'premium/assets/' : '')
773 : (defined('JLTMA_PATH') ? trailingslashit(JLTMA_PATH) . 'assets/' : '');
774
775 if ($base_dir) {
776 $file_path = $base_dir . $relative_file;
777 if (is_file($file_path)) {
778 return (string) filemtime($file_path);
779 }
780 }
781
782 return $fallback;
783 }
784
785 /**
786 * Get global assets registry
787 *
788 * These use standard global handles shared across plugins/themes
789 * to avoid duplicate loading (e.g., Font Awesome, Google Fonts).
790 *
791 * @return array
792 */
793 private static function get_global_assets()
794 {
795 $assets = [];
796
797 // Use Font Awesome bundled with Elementor (a required dependency) instead of a CDN.
798 if (defined('ELEMENTOR_ASSETS_URL')) {
799 $assets['font-awesome-5-all-css'] = [
800 'type' => 'css',
801 'url' => ELEMENTOR_ASSETS_URL . 'lib/font-awesome/css/all.min.css',
802 'deps' => [],
803 'version' => '6.5.1',
804 ];
805 }
806
807 return $assets;
808 }
809
810 /**
811 * Register global assets with standard handles
812 *
813 * Uses standard handles (e.g., 'font-awesome-5-all-css') so assets
814 * are not duplicated if already loaded by Elementor or other plugins.
815 */
816 private function register_global_assets()
817 {
818 $global_assets = self::get_global_assets();
819
820 foreach ($global_assets as $handle => $asset) {
821 // Skip if already registered by another plugin/theme
822 if ($asset['type'] === 'css' && wp_style_is($handle, 'registered')) {
823 continue;
824 }
825 if ($asset['type'] === 'js' && wp_script_is($handle, 'registered')) {
826 continue;
827 }
828
829 // Register the asset
830 if ($asset['type'] === 'css') {
831 wp_register_style($handle, $asset['url'], $asset['deps'], $asset['version']);
832 } else {
833 wp_register_script($handle, $asset['url'], $asset['deps'], $asset['version'], true);
834 }
835 }
836 }
837
838 /**
839 * Enqueue vendor assets by handle
840 *
841 * @param string|array $handles Single handle or array of handles
842 */
843 public static function enqueue($handles)
844 {
845 $handles = (array) $handles;
846
847 foreach ($handles as $handle) {
848 // Recursively enqueue dependencies first (for CSS - JS deps are handled by WP)
849 self::enqueue_with_deps($handle);
850 }
851 }
852
853 /**
854 * Enqueue a vendor with all its dependencies (including CSS)
855 * WordPress handles JS dependencies automatically, but not CSS
856 *
857 * @param string $handle Vendor handle
858 * @param array $enqueued Track already enqueued to avoid duplicates
859 */
860 private static function enqueue_with_deps($handle, &$enqueued = [])
861 {
862 if (isset($enqueued[$handle])) {
863 return;
864 }
865
866 $vendor = self::get($handle);
867 if (!$vendor) {
868 return;
869 }
870
871 // Enqueue dependencies first
872 foreach ($vendor['deps'] ?? [] as $dep) {
873 self::enqueue_with_deps($dep, $enqueued);
874 }
875
876 $wp_handle = 'jltma-' . $handle;
877
878 // Enqueue CSS if exists
879 if (!empty($vendor['files']['css'])) {
880 wp_enqueue_style($wp_handle);
881 }
882
883 // Enqueue JS if exists
884 if (!empty($vendor['files']['js'])) {
885 wp_enqueue_script($wp_handle);
886 }
887
888 $enqueued[$handle] = true;
889 }
890
891 /**
892 * Get WordPress handle for a vendor
893 *
894 * @param string $handle Vendor handle
895 * @return string WordPress handle with jltma- prefix
896 */
897 public static function get_wp_handle($handle)
898 {
899 return 'jltma-' . $handle;
900 }
901
902 /**
903 * Check if a vendor is deprecated
904 *
905 * @param string $handle Vendor handle
906 * @return bool
907 */
908 public static function is_deprecated($handle)
909 {
910 $vendor = self::get($handle);
911 return !empty($vendor['deprecated']);
912 }
913
914 /**
915 * Check if a vendor is pro-only
916 *
917 * @param string $handle Vendor handle
918 * @return bool
919 */
920 public static function is_pro($handle)
921 {
922 $vendor = self::get($handle);
923 return !empty($vendor['pro']);
924 }
925
926 /**
927 * Get all vendor handles
928 *
929 * @return array
930 */
931 public static function get_all_handles()
932 {
933 return array_keys(self::get_registry());
934 }
935
936 /**
937 * Check if a URL is external (CDN/full URL)
938 *
939 * @param string $url URL to check
940 * @return bool
941 */
942 private function is_external_url($url)
943 {
944 return strpos($url ?? '', '//') === 0 || strpos($url ?? '', 'http') === 0;
945 }
946
947 /**
948 * Debug: Get registration info for a vendor
949 *
950 * @param string $handle Vendor handle
951 * @return array
952 */
953 public static function debug_vendor($handle)
954 {
955 $vendor = self::get($handle);
956 if (!$vendor) {
957 return ['error' => 'Vendor not found: ' . $handle];
958 }
959
960 $wp_handle = 'jltma-' . $handle;
961 $all_deps = self::get_all_deps($handle);
962
963 return [
964 'handle' => $handle,
965 'wp_handle' => $wp_handle,
966 'files' => $vendor['files'] ?? [],
967 'deps' => $vendor['deps'] ?? [],
968 'all_deps' => $all_deps,
969 'path' => $vendor['path'] ?? 'vendor',
970 'deprecated' => !empty($vendor['deprecated']),
971 'pro' => !empty($vendor['pro']),
972 'css_registered' => wp_style_is($wp_handle, 'registered'),
973 'js_registered' => wp_script_is($wp_handle, 'registered'),
974 ];
975 }
976 }
977
978 // Initialize Assets Manager
979 Assets_Manager::get_instance();
980