PluginProbe ʕ •ᴥ•ʔ
EmbedPress – PDF Embedder, Embed PDF viewer, YouTube Videos, 3D FlipBook, Social feeds & more / 4.4.7
EmbedPress – PDF Embedder, Embed PDF viewer, YouTube Videos, 3D FlipBook, Social feeds & more v4.4.7
4.5.6 4.5.5 4.5.4 4.5.3 4.5.2 trunk 1.0.0 1.1.0 1.1.1 1.1.2 1.1.3 1.2.0 1.3.0 1.3.1 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.5.0 1.6.0 1.6.1 1.6.2 1.6.3 1.7.0 1.7.1 1.7.2 1.7.3 1.7.4 1.7.5 2.0.0 2.0.1 2.0.2 2.0.3 2.1.0 2.1.1 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.2.0 2.2.1 2.2.2 2.3.0 2.3.1 2.3.2 2.3.3 2.4.0 2.4.1 2.5.0 2.5.1 2.5.2 2.5.3 2.5.4 2.5.5 2.6.0 2.6.1 2.6.2 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.1.0 3.1.1 3.1.2 3.1.3 3.2.0 3.2.1 3.3.0 3.3.1 3.3.2 3.3.3 3.3.4 3.3.5 3.3.6 3.3.7 3.4.0 3.4.1 3.4.2 3.4.3 3.5.0 3.5.1 3.5.2 3.5.3 3.6.0 3.6.1 3.6.2 3.6.3 3.6.4 3.6.5 3.6.6 3.6.7 3.6.8 3.7.0 3.7.1 3.7.2 3.7.3 3.8.0 3.8.1 3.8.2 3.8.3 3.8.4 3.8.5 3.9.0 3.9.1 3.9.10 3.9.11 3.9.12 3.9.13 3.9.14 3.9.15 3.9.16 3.9.17 3.9.2 3.9.3 3.9.4 3.9.5 3.9.6 3.9.7 3.9.8 3.9.9 4.0.0 4.0.1 4.0.10 4.0.11 4.0.12 4.0.13 4.0.14 4.0.2 4.0.3 4.0.4 4.0.5 4.0.6 4.0.7 4.0.8 4.0.9 4.1.0 4.1.1 4.1.10 4.1.2 4.1.3 4.1.4 4.1.5 4.1.6 4.1.7 4.1.8 4.1.9 4.2.0 4.2.1 4.2.2 4.2.3 4.2.4 4.2.5 4.2.6 4.2.7 4.2.8 4.2.9 4.3.0 4.3.1 4.4.0 4.4.1 4.4.10 4.4.11 4.4.2 4.4.3 4.4.4 4.4.5 4.4.6 4.4.7 4.4.8 4.4.9 4.5.0 4.5.1
embedpress / Core / AssetManager.php
embedpress / Core Last commit date
AssetManager.php 6 months ago LocalizationManager.php 6 months ago init.php 9 months ago
AssetManager.php
1516 lines
1 <?php
2
3 namespace EmbedPress\Core;
4
5 // Include LocalizationManager
6 // require_once __DIR__ . '/LocalizationManager.php';
7
8 use Embedpress\Core\LocalizationManager;
9 use EmbedPress\Includes\Classes\Helper;
10
11 /**
12 * EmbedPress Asset Manager
13 *
14 * Centralized asset management for JS/CSS files across blocks, Elementor, admin, and frontend
15 * Handles new build files from Vite build system
16 */
17 class AssetManager
18 {
19 /**
20 * Track which handles should be treated as ES modules
21 */
22 private static $module_handles = [];
23
24 /**
25 * Track if the global module filter has been added
26 */
27 private static $module_filter_added = false;
28
29 /**
30 * Cache for content detection to avoid multiple checks
31 */
32 private static $has_embedpress_content = null;
33
34 /**
35 * Cache for custom player detection
36 */
37 private static $custom_player_enabled = null;
38
39 /**
40 * Cache for detected embed types on current page
41 */
42 private static $detected_embed_types = null;
43
44 /**
45 * Asset definitions with context-based loading
46 */
47 private static $assets = [
48
49 // 🔧 Scripts (Ordered by Priority)
50 // ----------------------------------
51
52 // Vendor assets (copied to assets folder for consistency)
53 // Priority 1-5: Core vendor libraries
54 'plyr-css' => [
55 'file' => 'css/plyr.css',
56 'deps' => [],
57 'contexts' => ['frontend', 'elementor', 'editor'],
58 'type' => 'style',
59 'handle' => 'embedpress-plyr-css',
60 'priority' => 1,
61 'condition' => 'custom_player', // Only load if custom player is enabled
62 'providers' => ['youtube', 'vimeo', 'video', 'audio'], // Only for these providers
63 ],
64 'carousel-vendor-css' => [
65 'file' => 'css/carousel.min.css',
66 'deps' => [],
67 'contexts' => ['frontend', 'elementor'],
68 'type' => 'style',
69 'handle' => 'embedpress-carousel-vendor-css',
70 'priority' => 1,
71 'condition' => 'has_content', // Load whenever EmbedPress content is present since front.js depends on it
72 ],
73 'glider-css' => [
74 'file' => 'css/glider.min.css',
75 'deps' => [],
76 'contexts' => ['frontend', 'elementor'],
77 'type' => 'style',
78 'handle' => 'embedpress-glider-css',
79 'priority' => 1,
80 'providers' => ['youtube-channel', 'youtube-live', 'instagram', 'opensea'], // Only for carousel-based embeds
81 ],
82 'plyr-js' => [
83 'file' => 'js/vendor/plyr.js',
84 'deps' => ['jquery'],
85 'contexts' => ['frontend', 'elementor', 'editor'],
86 'type' => 'script',
87 'footer' => true,
88 'handle' => 'embedpress-plyr',
89 'priority' => 2,
90 'condition' => 'custom_player', // Only load if custom player is enabled
91 'providers' => ['youtube', 'vimeo', 'video', 'audio'], // Only for these providers
92 ],
93 'carousel-vendor-js' => [
94 'file' => 'js/vendor/carousel.min.js',
95 'deps' => ['jquery'],
96 'contexts' => ['frontend', 'elementor'],
97 'type' => 'script',
98 'footer' => true,
99 'handle' => 'embedpress-carousel-vendor',
100 'priority' => 2,
101 'condition' => 'has_content', // Load whenever EmbedPress content is present since front.js depends on it
102 ],
103 'glider-js' => [
104 'file' => 'js/vendor/glider.min.js',
105 'deps' => ['jquery'],
106 'contexts' => ['frontend', 'elementor'],
107 'type' => 'script',
108 'footer' => true,
109 'handle' => 'embedpress-glider',
110 'priority' => 2,
111 'providers' => ['youtube-channel', 'youtube-live', 'instagram', 'opensea'], // Only for carousel-based embeds
112 ],
113 'pdfobject-js' => [
114 'file' => 'js/vendor/pdfobject.js',
115 'deps' => [],
116 'contexts' => ['frontend', 'elementor', 'editor'],
117 'type' => 'script',
118 'footer' => true,
119 'handle' => 'embedpress-pdfobject',
120 'priority' => 2,
121 'providers' => ['pdf', 'document'], // Only for PDF/document embeds
122 ],
123 'vimeo-player-js' => [
124 'file' => 'js/vendor/vimeo-player.js',
125 'deps' => [],
126 'contexts' => ['frontend', 'elementor'],
127 'type' => 'script',
128 'footer' => true,
129 'handle' => 'embedpress-vimeo-player',
130 'priority' => 2,
131 'providers' => ['vimeo'], // Only for Vimeo embeds
132 ],
133 'ytiframeapi-js' => [
134 'file' => 'js/vendor/ytiframeapi.js',
135 'deps' => [],
136 'contexts' => ['frontend', 'elementor'],
137 'type' => 'script',
138 'footer' => true,
139 'handle' => 'embedpress-ytiframeapi',
140 'priority' => 2,
141 'providers' => ['youtube', 'youtube-channel', 'youtube-live', 'youtube-shorts'], // Only for YouTube embeds
142 ],
143
144 // Priority 5-6: Main application build assets
145 'admin-js' => [
146 'file' => 'js/admin.build.js',
147 'deps' => [],
148 'contexts' => ['admin'],
149 'type' => 'script',
150 'footer' => true,
151 'handle' => 'embedpress-admin',
152 'priority' => 5,
153 'page' => 'embedpress'
154 ],
155 // Priority 7-10: Blocks
156 'blocks-js' => [
157 'file' => 'js/blocks.build.js',
158 'deps' => ['wp-blocks', 'wp-i18n', 'wp-element', 'wp-api-fetch', 'wp-is-shallow-equal', 'wp-editor', 'wp-components'],
159 'contexts' => ['editor'],
160 'type' => 'script',
161 'footer' => true,
162 'handle' => 'embedpress-blocks-editor',
163 'priority' => 10,
164 ],
165 'blocks-editor-style' => [
166 'file' => 'css/blocks.build.css',
167 'deps' => [],
168 'contexts' => ['editor'],
169 'type' => 'style',
170 'handle' => 'embedpress-blocks-editor-style',
171 'priority' => 10,
172 ],
173 'blocks-style' => [
174 'file' => 'css/blocks.build.css',
175 'deps' => [],
176 'contexts' => ['frontend', 'editor'],
177 'type' => 'style',
178 'handle' => 'embedpress-blocks-style',
179 'priority' => 10,
180 ],
181 'lazy-load-css' => [
182 'file' => 'css/lazy-load.css',
183 'deps' => [],
184 'contexts' => ['frontend', 'elementor'],
185 'type' => 'style',
186 'handle' => 'embedpress-lazy-load-css',
187 'priority' => 10,
188 // 'condition' => 'lazy_load', // Only load when lazy loading is enabled
189 ],
190
191 // Priority 15-20: Legacy JS files
192 'admin-legacy-js' => [
193 'file' => 'js/admin.js',
194 'deps' => ['jquery'],
195 'contexts' => ['admin'],
196 'type' => 'script',
197 'footer' => true,
198 'handle' => 'embedpress-admin-legacy',
199 'priority' => 15,
200 'page' => 'embedpress'
201 ],
202 'ads-js' => [
203 'file' => 'js/sponsored.js',
204 'deps' => ['jquery', 'embedpress-front'],
205 'contexts' => ['editor', 'frontend', 'elementor'],
206 'type' => 'script',
207 'footer' => true,
208 'handle' => 'embedpress-ads',
209 'priority' => 16,
210 'condition' => 'has_content', // Load for any EmbedPress content (ads can be on any embed)
211 ],
212 'lazy-load-js' => [
213 'file' => 'js/lazy-load.js',
214 'deps' => [],
215 'contexts' => ['frontend', 'elementor'],
216 'type' => 'script',
217 'footer' => true,
218 'handle' => 'embedpress-lazy-load',
219 'priority' => 16,
220 // 'condition' => 'lazy_load', // Only load when lazy loading is enabled
221 ],
222 'analytics-tracker-js' => [
223 'file' => 'js/analytics-tracker.js',
224 'deps' => ['jquery'],
225 'contexts' => ['frontend', 'elementor'],
226 'type' => 'script',
227 'footer' => true,
228 'handle' => 'embedpress-analytics-tracker',
229 'priority' => 15,
230 'condition' => 'has_content', // Load for any EmbedPress content (analytics track all embeds)
231 ],
232 'carousel-js' => [
233 'file' => 'js/carousel.js',
234 'deps' => ['jquery', 'embedpress-carousel-vendor'],
235 'contexts' => ['frontend', 'elementor'],
236 'type' => 'script',
237 'footer' => true,
238 'handle' => 'embedpress-carousel',
239 'priority' => 15,
240 'providers' => ['youtube-channel', 'youtube-live', 'instagram', 'opensea'], // Only for carousel-based embeds
241 ],
242 'documents-viewer-js' => [
243 'file' => 'js/documents-viewer-script.js',
244 'deps' => ['jquery'],
245 'contexts' => ['frontend', 'elementor'],
246 'type' => 'script',
247 'footer' => true,
248 'handle' => 'embedpress-documents-viewer',
249 'priority' => 15,
250 'providers' => ['document', 'google-docs', 'google-sheets', 'google-slides'], // Only for document embeds
251 ],
252 'front-js' => [
253 'file' => 'js/front.js',
254 'deps' => ['jquery', 'embedpress-carousel-vendor'],
255 'contexts' => ['frontend', 'editor', 'elementor'],
256 'type' => 'script',
257 'footer' => true,
258 'handle' => 'embedpress-front',
259 'priority' => 15,
260 'condition' => 'has_content', // Core script - load for any EmbedPress content
261 ],
262 'gallery-justify-js' => [
263 'file' => 'js/gallery-justify.js',
264 'deps' => ['jquery'],
265 'contexts' => ['editor', 'frontend', 'elementor'],
266 'type' => 'script',
267 'footer' => true,
268 'handle' => 'embedpress-gallery-justify',
269 'priority' => 15,
270 'providers' => ['google-photos', 'instagram'], // Only for gallery-based embeds
271 ],
272 'gutenberg-script-js' => [
273 'file' => 'js/gutneberg-script.js',
274 'deps' => ['wp-blocks', 'wp-element'],
275 'contexts' => ['editor'],
276 'type' => 'script',
277 'footer' => true,
278 'handle' => 'embedpress-gutenberg-script',
279 'priority' => 15,
280 ],
281 'init-plyr-js' => [
282 'file' => 'js/initplyr.js',
283 'deps' => ['jquery', 'embedpress-plyr'],
284 'contexts' => ['frontend', 'elementor'],
285 'type' => 'script',
286 'footer' => true,
287 'handle' => 'embedpress-init-plyr',
288 'priority' => 15,
289 'condition' => 'custom_player', // Only load if custom player is enabled
290 'providers' => ['youtube', 'vimeo', 'video', 'audio'], // Only for these providers
291 ],
292 'instafeed-js' => [
293 'file' => 'js/instafeed.js',
294 'deps' => ['jquery'],
295 'contexts' => ['frontend', 'elementor'],
296 'type' => 'script',
297 'footer' => true,
298 'handle' => 'embedpress-instafeed',
299 'priority' => 15,
300 'providers' => ['instagram'], // Only for Instagram embeds
301 ],
302 'license-js' => [
303 'file' => 'js/license.js',
304 'deps' => ['jquery'],
305 'contexts' => ['admin'],
306 'type' => 'script',
307 'footer' => true,
308 'handle' => 'embedpress-license',
309 'priority' => 15,
310 'page' => 'embedpress'
311 ],
312 'feature-notices-js' => [
313 'file' => 'js/feature-notices.js',
314 'deps' => ['jquery'],
315 'contexts' => ['admin'],
316 'type' => 'script',
317 'footer' => true,
318 'handle' => 'embedpress-feature-notices',
319 'priority' => 15,
320 // 'page' => 'embedpress'
321 ],
322 'preview-js' => [
323 'file' => 'js/preview.js',
324 'deps' => ['jquery'],
325 'contexts' => ['classic_editor'],
326 'type' => 'script',
327 'footer' => true,
328 'handle' => 'embedpress-preview',
329 'priority' => 15,
330 ],
331 'settings-js' => [
332 'file' => 'js/settings.js',
333 'deps' => ['jquery', 'wp-color-picker'],
334 'contexts' => ['admin'],
335 'type' => 'script',
336 'footer' => true,
337 'handle' => 'embedpress-settings',
338 'priority' => 15,
339 'page' => 'embedpress'
340 ],
341
342 // 🎨 Styles (Ordered by Priority)
343 // ----------------------------------
344
345 // Build CSS files (analytics.build.css is handled by Analytics.php)
346
347 // Legacy CSS files
348 'admin-notices-css' => [
349 'file' => 'css/admin-notices.css',
350 'deps' => [],
351 'contexts' => ['admin'],
352 'type' => 'style',
353 'handle' => 'embedpress-admin-notices',
354 'priority' => 5,
355 // 'page' => 'embedpress'
356 ],
357 'feature-notices-css' => [
358 'file' => 'css/feature-notices.css',
359 'deps' => [],
360 'contexts' => ['admin'],
361 'type' => 'style',
362 'handle' => 'embedpress-feature-notices',
363 'priority' => 5,
364 ],
365
366 'el-icon-css' => [
367 'file' => 'css/el-icon.css',
368 'deps' => [],
369 'contexts' => ['elementor-editor'],
370 'type' => 'style',
371 'handle' => 'embedpress-el-icon',
372 'priority' => 5,
373 ],
374 'embedpress-elementor-css' => [
375 'file' => 'css/embedpress-elementor.css',
376 'deps' => [],
377 'contexts' => ['elementor'],
378 'type' => 'style',
379 'handle' => 'embedpress-elementor-css',
380 'priority' => 5,
381 ],
382 'embedpress-css' => [
383 'file' => 'css/embedpress.css',
384 'deps' => [],
385 'contexts' => ['editor', 'frontend', 'elementor'],
386 'type' => 'style',
387 'handle' => 'embedpress-css',
388 'priority' => 5,
389 ],
390 'modal-css' => [
391 'file' => 'css/modal.css',
392 'deps' => [],
393 'contexts' => ['editor', 'classic_editor'],
394 'type' => 'style',
395 'handle' => 'embedpress-classic-editor-modal',
396 'priority' => 6,
397 ],
398 'meetup-events-css' => [
399 'file' => 'css/meetup-events.css',
400 'deps' => ['embedpress-css'],
401 'contexts' => ['frontend', 'editor', 'elementor'],
402 'type' => 'style',
403 'handle' => 'embedpress-meetup-events',
404 'providers' => ['meetup'], // Only for Meetup embeds
405 'priority' => 6,
406 ],
407 'settings-icons-css' => [
408 'file' => 'css/settings-icons.css',
409 'deps' => [],
410 'contexts' => ['admin'],
411 'type' => 'style',
412 'handle' => 'embedpress-settings-icons',
413 'priority' => 5,
414 'page' => 'embedpress'
415 ],
416 'settings-css' => [
417 'file' => 'css/settings.css',
418 'deps' => [],
419 'contexts' => ['admin'],
420 'type' => 'style',
421 'handle' => 'embedpress-settings-css',
422 'priority' => 5,
423 'page' => 'embedpress'
424 ],
425 'admin-css' => [
426 'file' => 'css/admin.css',
427 'deps' => [],
428 'contexts' => ['admin'],
429 'type' => 'style',
430 'handle' => 'embedpress-admin-css',
431 'priority' => 5,
432 'page' => 'embedpress'
433 ],
434 ];
435
436 /**
437 * Initialize asset manager
438 */
439 public static function init()
440 {
441 // Register all assets early so they're available as dependencies for Elementor and other plugins
442 add_action('wp_enqueue_scripts', [__CLASS__, 'register_all_assets'], 1);
443 add_action('admin_enqueue_scripts', [__CLASS__, 'register_all_assets'], 1);
444
445 // Use proper priorities to ensure correct load order
446 add_action('wp_enqueue_scripts', [__CLASS__, 'enqueue_frontend_assets'], 5);
447 add_action('admin_enqueue_scripts', [__CLASS__, 'enqueue_admin_assets'], 5);
448 add_action('admin_enqueue_scripts', [__CLASS__, 'enqueue_classic_editor_assets'], 5);
449 add_action('enqueue_block_assets', [__CLASS__, 'enqueue_block_assets'], 5);
450
451
452 add_action('enqueue_block_editor_assets', [__CLASS__, 'enqueue_editor_assets'], 5);
453
454 // Elementor preview (frontend iframe) - enqueue after scripts are enqueued
455 add_action('elementor/frontend/after_enqueue_scripts', [__CLASS__, 'enqueue_elementor_assets'], 5);
456
457 // In Elementor editor, WP_Scripts registry is reset; re-register our assets before enqueuing
458 add_action('elementor/editor/before_enqueue_scripts', [__CLASS__, 'register_all_assets'], 1);
459 // Elementor editor panel (admin) - enqueue after editor scripts are enqueued
460 add_action('elementor/editor/after_enqueue_scripts', [__CLASS__, 'enqueue_elementor_editor_assets'], 5);
461 }
462
463 /**
464 * Register all assets early so they're available as dependencies
465 * This is crucial for Elementor widgets that declare script/style dependencies
466 */
467 public static function register_all_assets()
468 {
469 foreach (self::$assets as $key => $asset) {
470 $file_url = EMBEDPRESS_PLUGIN_DIR_URL . 'assets/' . $asset['file'];
471 $file_path = EMBEDPRESS_PLUGIN_DIR_PATH . '/assets/' . $asset['file'];
472
473 if (!file_exists($file_path)) {
474 continue;
475 }
476
477 $version = filemtime($file_path);
478
479 // Register (not enqueue) all assets
480 if ($asset['type'] === 'script') {
481 wp_register_script(
482 $asset['handle'],
483 $file_url,
484 $asset['deps'],
485 $version,
486 !empty($asset['footer'])
487 );
488
489 // Add module attribute for ES modules (only build files)
490 if (strpos($asset['file'], '.build.js') !== false) {
491 // Track this handle as a module
492 self::$module_handles[] = $asset['handle'];
493
494 // Add the global filter only once
495 if (!self::$module_filter_added) {
496 self::$module_filter_added = true;
497 add_filter('script_loader_tag', [__CLASS__, 'add_module_attribute'], 10, 2);
498 }
499 }
500 } elseif ($asset['type'] === 'style') {
501 wp_register_style(
502 $asset['handle'],
503 $file_url,
504 $asset['deps'],
505 $version,
506 $asset['media'] ?? 'all'
507 );
508 }
509 }
510 }
511
512 /**
513 * Enqueue frontend assets
514 */
515 public static function enqueue_frontend_assets()
516 {
517 self::enqueue_assets_for_context('frontend');
518
519 // Setup frontend localization
520 LocalizationManager::setup_frontend_localization();
521 }
522
523 /**
524 * Enqueue admin assets
525 */
526 public static function enqueue_admin_assets($hook = '')
527 {
528 self::enqueue_assets_for_context('admin', $hook);
529
530 // Load settings assets only on EmbedPress settings pages
531 if (strpos($hook, 'embedpress') !== false) {
532 self::enqueue_assets_for_context('settings', $hook);
533
534 // Ensure wp-color-picker is loaded for settings page
535 wp_enqueue_style('wp-color-picker');
536
537 // Ensure media scripts are loaded
538 if (!did_action('wp_enqueue_media')) {
539 wp_enqueue_media();
540 }
541 }
542
543 // Setup admin localization
544 LocalizationManager::setup_admin_localization($hook);
545 }
546
547 /**
548 * Enqueue block assets (both editor and frontend)
549 */
550 public static function enqueue_block_assets()
551 {
552 // This runs on both frontend and editor for blocks
553 // For frontend, we don't need the editor scripts
554 if (is_admin()) {
555 self::enqueue_assets_for_context('editor');
556 }
557 }
558
559 /**
560 * Enqueue editor-only assets
561 */
562 public static function enqueue_editor_assets()
563 {
564 // Ensure editor assets are loaded
565 self::enqueue_assets_for_context('editor');
566
567 // Setup editor localization
568 LocalizationManager::setup_editor_localization();
569 }
570
571 public static function enqueue_classic_editor_assets()
572 {
573
574 // Ensure editor assets are loaded
575 self::enqueue_assets_for_context('classic_editor');
576
577 // Setup editor localization
578 LocalizationManager::setup_editor_localization();
579 }
580
581 /**
582 * Enqueue Elementor frontend assets
583 */
584 public static function enqueue_elementor_assets()
585 {
586 self::enqueue_assets_for_context('elementor');
587
588 // Setup Elementor localization
589 LocalizationManager::setup_elementor_localization();
590 }
591
592 /**
593 * Enqueue Elementor editor assets
594 */
595 public static function enqueue_elementor_editor_assets()
596 {
597 // In Elementor editor, load only elementor and elementor-editor contexts
598 // Do NOT load 'editor' context - that's for Gutenberg only
599 self::enqueue_assets_for_context('elementor');
600 self::enqueue_assets_for_context('elementor-editor');
601
602 // Setup Elementor editor localization
603 LocalizationManager::setup_elementor_localization();
604 }
605
606 /**
607 * Enqueue assets for a specific context
608 */
609 private static function enqueue_assets_for_context($context, $hook = '')
610 {
611
612 $assets_to_enqueue = [];
613
614 // Collect assets for this context
615 foreach (self::$assets as $key => $asset) {
616 if (in_array($context, $asset['contexts'])) {
617 // Check if asset has page restriction
618 if (isset($asset['page']) && !empty($asset['page'])) {
619 // Only enqueue if we're on the specified page
620 if (strpos($hook, $asset['page']) !== false) {
621 $assets_to_enqueue[] = array_merge($asset, ['key' => $key]);
622 }
623 // If page doesn't match, don't enqueue this asset
624 } else {
625 // No page restriction, enqueue normally
626 $assets_to_enqueue[] = array_merge($asset, ['key' => $key]);
627 }
628 }
629 }
630
631 // Sort by priority
632 usort($assets_to_enqueue, function ($a, $b) {
633 return $a['priority'] - $b['priority'];
634 });
635
636 // Enqueue assets
637 foreach ($assets_to_enqueue as $asset) {
638 self::enqueue_single_asset($asset);
639 }
640 }
641
642 /**
643 * Enqueue a single asset (assumes asset is already registered)
644 */
645 private static function enqueue_single_asset($asset)
646 {
647 $file_path = EMBEDPRESS_PLUGIN_DIR_PATH . '/assets/' . $asset['file'];
648
649 if (! file_exists($file_path)) {
650 return;
651 }
652
653 // Check if we should load this asset based on current context
654 if (!self::should_load_asset($asset)) {
655 return;
656 }
657
658 // Enqueue the already-registered asset
659 if ($asset['type'] === 'script') {
660 wp_enqueue_script($asset['handle']);
661 } elseif ($asset['type'] === 'style') {
662 wp_enqueue_style($asset['handle']);
663 }
664 }
665
666 /**
667 * Determine if an asset should be loaded based on current context
668 */
669 private static function should_load_asset($asset)
670 {
671 // Check conditional loading requirements first
672 if (isset($asset['condition'])) {
673 if (!self::check_asset_condition($asset['condition'])) {
674 return false;
675 }
676 }
677
678 // Check provider-specific loading
679 if (isset($asset['providers']) && !empty($asset['providers'])) {
680 if (!self::check_provider_match($asset['providers'])) {
681 return false;
682 }
683 }
684
685 // Get current environment state
686 $is_admin = is_admin();
687 $is_elementor_editor = false;
688 $is_elementor_preview = false;
689 $is_gutenberg_editor = false;
690
691 // Check Elementor states
692 if (class_exists('\Elementor\Plugin')) {
693 $elementor = \Elementor\Plugin::$instance;
694
695 if (isset($elementor->editor)) {
696 $is_elementor_editor = $elementor->editor->is_edit_mode();
697 }
698
699 if (isset($elementor->preview)) {
700 $is_elementor_preview = $elementor->preview->is_preview_mode();
701 }
702 }
703
704 // Check if we're in Gutenberg editor
705 if ($is_admin) {
706 global $pagenow;
707 $is_gutenberg_editor = (
708 $pagenow === 'post.php' ||
709 $pagenow === 'post-new.php' ||
710 $pagenow === 'site-editor.php'
711 ) && function_exists('use_block_editor_for_post_type');
712
713 // Check if we're in classic editor (not Gutenberg)
714 $is_classic_editor = false;
715 if ($pagenow === 'post.php' || $pagenow === 'post-new.php') {
716 // Check if classic editor is being used
717 if (
718 isset($_GET['classic-editor']) ||
719 (function_exists('use_block_editor_for_post_type') &&
720 isset($_GET['post']) &&
721 !use_block_editor_for_post_type(get_post_type($_GET['post'])))
722 ) {
723 $is_classic_editor = true;
724 }
725 // Also check if Classic Editor plugin is active and set to classic mode
726 if (
727 class_exists('Classic_Editor') &&
728 get_option('classic-editor-replace') === 'classic'
729 ) {
730 $is_classic_editor = true;
731 }
732 }
733 }
734
735 // Asset loading logic based on contexts
736 foreach ($asset['contexts'] as $context) {
737
738
739 switch ($context) {
740 case 'frontend':
741 // Load on frontend (not in any editor or admin)
742 if (!$is_admin && !$is_elementor_editor && !$is_elementor_preview) {
743 return true;
744 }
745 break;
746
747 case 'admin':
748 // Load in WordPress admin (but not in Elementor editor)
749 if ($is_admin && !$is_elementor_editor && !$is_elementor_preview) {
750 // Check if asset has page restriction
751 if (isset($asset['page'])) {
752 return self::is_embedpress_admin_page($asset['page']);
753 }
754 return true;
755 }
756 break;
757
758 case 'editor':
759 // Load ONLY in Gutenberg editor (not in Elementor editor or other admin pages)
760 if ($is_gutenberg_editor && !$is_elementor_editor) {
761 return true;
762 }
763 break;
764 case 'classic_editor':
765 // Load only in classic editor (TinyMCE)
766 if ($is_classic_editor) {
767 return true;
768 }
769 break;
770 case 'elementor':
771 // Load in Elementor editor, preview, or frontend when Elementor is rendering
772 if ($is_elementor_editor || $is_elementor_preview) {
773 return true;
774 }
775 // Also load on frontend if Elementor content is present
776 if (!$is_admin && self::has_elementor_content()) {
777 return true;
778 }
779 break;
780
781 case 'elementor-editor':
782
783 // Load only in Elementor editor (not preview or frontend)
784 if ($is_elementor_editor) {
785 return true;
786 }
787 break;
788
789 case 'settings':
790 // Load only on EmbedPress settings pages
791 if ($is_admin && !$is_elementor_editor && !$is_elementor_preview) {
792 return true;
793 }
794 break;
795 }
796 }
797
798 // Check if this is an individual block script and if it should be loaded
799 if (strpos($asset['handle'], 'embedpress-block-') === 0) {
800 return self::should_load_individual_block($asset['handle']);
801 }
802
803 return false;
804 }
805
806 /**
807 * Check if we're on an EmbedPress admin page
808 */
809 private static function is_embedpress_admin_page($page_type)
810 {
811 global $pagenow;
812
813 // Get current page
814 $current_page = isset($_GET['page']) ? $_GET['page'] : '';
815
816 switch ($page_type) {
817 case 'embedpress':
818 // Check if we're on any EmbedPress admin page
819 return (
820 strpos($current_page, 'embedpress') !== false ||
821 $pagenow === 'admin.php' && strpos($current_page, 'embedpress') !== false
822 );
823 case 'embedpress-analytics':
824 return $current_page === 'embedpress-analytics';
825 default:
826 return false;
827 }
828 }
829
830 /**
831 * Check if individual block should be loaded based on active blocks
832 */
833 private static function should_load_individual_block($handle)
834 {
835 // Get active blocks from settings
836 $elements = (array) get_option(EMBEDPRESS_PLG_NAME . ":elements", []);
837 $active_blocks = isset($elements['gutenberg']) ? (array) $elements['gutenberg'] : [];
838
839 // Map handles to block names
840 $block_map = [
841 'embedpress-block-embedpress' => 'embedpress',
842 'embedpress-block-document' => 'document',
843 'embedpress-block-pdf' => 'embedpress-pdf',
844 'embedpress-block-calendar' => 'embedpress-calendar',
845 'embedpress-block-google-docs' => 'google-docs',
846 'embedpress-block-google-drawings' => 'google-drawings',
847 'embedpress-block-google-forms' => 'google-forms',
848 'embedpress-block-google-maps' => 'google-maps',
849 'embedpress-block-google-sheets' => 'google-sheets',
850 'embedpress-block-google-slides' => 'google-slides',
851 'embedpress-block-twitch' => 'twitch',
852 'embedpress-block-wistia' => 'wistia',
853 'embedpress-block-youtube' => 'youtube'
854 ];
855
856 $block_name = isset($block_map[$handle]) ? $block_map[$handle] : '';
857
858 // If no block name found or no active blocks set, load all blocks (default behavior)
859 if (empty($block_name) || empty($active_blocks)) {
860 return true;
861 }
862
863 // Check if this specific block is active
864 return in_array($block_name, $active_blocks);
865 }
866
867 /**
868 * Check if current page has Elementor content
869 */
870 private static function has_elementor_content()
871 {
872 if (! class_exists('\Elementor\Plugin')) {
873 return false;
874 }
875
876 if (is_singular()) {
877 $post_id = get_the_ID();
878 if (empty($post_id) || ! is_numeric($post_id)) {
879 return false;
880 }
881
882 $document = \Elementor\Plugin::$instance->documents->get($post_id);
883
884 if ($document && method_exists($document, 'is_built_with_elementor')) {
885 return (bool) $document->is_built_with_elementor();
886 }
887 }
888
889 return false;
890 }
891
892
893 /**
894 * Get asset URL
895 */
896 public static function get_asset_url($file)
897 {
898 return EMBEDPRESS_PLUGIN_DIR_URL . 'assets/' . $file;
899 }
900
901
902
903 /**
904 * Add module attribute to script tags for ES modules
905 */
906 public static function add_module_attribute($tag, $handle)
907 {
908 if (in_array($handle, self::$module_handles)) {
909 // Only add type="module" if it doesn't already exist
910 if (strpos($tag, 'type="module"') === false) {
911 return str_replace('<script ', '<script type="module" ', $tag);
912 }
913 }
914 return $tag;
915 }
916
917 /**
918 * Check if asset exists
919 */
920 public static function asset_exists($file)
921 {
922 $plugin_path = dirname(dirname(dirname(__DIR__)));
923 return file_exists($plugin_path . '/assets/' . $file);
924 }
925
926 /**
927 * Check if an asset condition is met
928 *
929 * @param string $condition The condition to check
930 * @return bool
931 */
932 private static function check_asset_condition($condition)
933 {
934 switch ($condition) {
935 case 'custom_player':
936 return self::is_custom_player_enabled();
937
938 case 'has_content':
939 // In Elementor editor, always load core scripts with has_content condition
940 // because we can't detect unsaved content
941 if (class_exists('\Elementor\Plugin')) {
942 $elementor = \Elementor\Plugin::$instance;
943 if (isset($elementor->editor) && $elementor->editor->is_edit_mode()) {
944 return true;
945 }
946 }
947 return self::has_embedpress_content();
948
949 case 'lazy_load':
950 return self::has_lazy_load_enabled();
951
952 case 'always':
953 default:
954 return true;
955 }
956 }
957
958 /**
959 * Check if custom player is enabled on the current page
960 *
961 * @return bool
962 */
963 private static function is_custom_player_enabled()
964 {
965 // Cache the result to avoid multiple checks
966 if (self::$custom_player_enabled !== null) {
967 return self::$custom_player_enabled;
968 }
969
970 // In Elementor editor, always load custom player scripts to allow live preview
971 // because we can't detect unsaved widget settings
972 if (class_exists('\Elementor\Plugin')) {
973 $elementor = \Elementor\Plugin::$instance;
974 if (isset($elementor->editor) && $elementor->editor->is_edit_mode()) {
975 self::$custom_player_enabled = true;
976 return true;
977 }
978 }
979
980 global $post;
981
982 if (!$post) {
983 self::$custom_player_enabled = false;
984 return false;
985 }
986
987 $content = $post->post_content;
988
989 // Check for custom player in Gutenberg blocks
990 if (function_exists('has_blocks') && has_blocks($content)) {
991 $blocks = parse_blocks($content);
992 if (self::has_custom_player_in_blocks($blocks)) {
993 self::$custom_player_enabled = true;
994 return true;
995 }
996 }
997
998 // Check for custom player in Elementor
999 if (class_exists('\Elementor\Plugin')) {
1000 $document = \Elementor\Plugin::$instance->documents->get($post->ID);
1001 if ($document && method_exists($document, 'is_built_with_elementor') && $document->is_built_with_elementor()) {
1002 // Check Elementor meta for custom player settings
1003 $elementor_data = get_post_meta($post->ID, '_elementor_data', true);
1004 if ($elementor_data && is_string($elementor_data) && (strpos($elementor_data, 'emberpress_custom_player') !== false || strpos($elementor_data, '"customPlayer":true') !== false)) {
1005 self::$custom_player_enabled = true;
1006 return true;
1007 }
1008 }
1009 }
1010
1011 // Check for custom player in shortcodes (look for customPlayer attribute)
1012 if (has_shortcode($content, 'embedpress')) {
1013 if (is_string($content) && (strpos($content, 'customPlayer') !== false || strpos($content, 'custom_player') !== false)) {
1014 self::$custom_player_enabled = true;
1015 return true;
1016 }
1017 }
1018
1019 self::$custom_player_enabled = false;
1020 return false;
1021 }
1022
1023 /**
1024 * Check if blocks contain custom player settings
1025 *
1026 * @param array $blocks
1027 * @return bool
1028 */
1029 private static function has_custom_player_in_blocks($blocks)
1030 {
1031 foreach ($blocks as $block) {
1032 // Check if this is an EmbedPress block with custom player enabled
1033 $block_name = $block['blockName'] ?? '';
1034 if ($block_name && strpos($block_name, 'embedpress/') === 0) {
1035 if (isset($block['attrs']['customPlayer']) && $block['attrs']['customPlayer']) {
1036 return true;
1037 }
1038 }
1039
1040 // Recursively check inner blocks
1041 if (!empty($block['innerBlocks'])) {
1042 if (self::has_custom_player_in_blocks($block['innerBlocks'])) {
1043 return true;
1044 }
1045 }
1046 }
1047
1048 return false;
1049 }
1050
1051 /**
1052 * Check if lazy loading is enabled in any embed on the page
1053 *
1054 * @return bool
1055 */
1056 private static function has_lazy_load_enabled()
1057 {
1058 global $post;
1059
1060 if (!$post) {
1061 return false;
1062 }
1063
1064 $content = $post->post_content;
1065
1066 // Check if post content contains lazy load attributes in blocks
1067 if (function_exists('has_blocks') && has_blocks($content)) {
1068 $blocks = parse_blocks($content);
1069 if (self::has_lazy_load_in_blocks($blocks)) {
1070 return true;
1071 }
1072 }
1073
1074 // Check for Elementor meta (if Elementor is active)
1075 if (class_exists('\Elementor\Plugin')) {
1076 $document = \Elementor\Plugin::$instance->documents->get($post->ID);
1077 if ($document && method_exists($document, 'is_built_with_elementor') && $document->is_built_with_elementor()) {
1078 $elementor_data = get_post_meta($post->ID, '_elementor_data', true);
1079 if ($elementor_data && is_string($elementor_data) && strpos($elementor_data, '"enable_lazy_load":"yes"') !== false) {
1080 return true;
1081 }
1082 }
1083 }
1084
1085 return false;
1086 }
1087
1088 /**
1089 * Check if blocks contain lazy load settings
1090 *
1091 * @param array $blocks
1092 * @return bool
1093 */
1094 private static function has_lazy_load_in_blocks($blocks)
1095 {
1096 foreach ($blocks as $block) {
1097 // Check if this is an EmbedPress block with lazy load enabled
1098 $block_name = $block['blockName'] ?? '';
1099 if ($block_name && strpos($block_name, 'embedpress/') === 0) {
1100 if (isset($block['attrs']['enableLazyLoad']) && $block['attrs']['enableLazyLoad']) {
1101 return true;
1102 }
1103 }
1104
1105 // Recursively check inner blocks
1106 if (!empty($block['innerBlocks'])) {
1107 if (self::has_lazy_load_in_blocks($block['innerBlocks'])) {
1108 return true;
1109 }
1110 }
1111 }
1112
1113 return false;
1114 }
1115
1116
1117 /**
1118 * Check if current page has EmbedPress content
1119 *
1120 * @return bool
1121 */
1122 private static function has_embedpress_content()
1123 {
1124 // Cache the result to avoid multiple checks
1125 if (self::$has_embedpress_content !== null) {
1126 return self::$has_embedpress_content;
1127 }
1128
1129 global $post;
1130
1131 if (!$post) {
1132 self::$has_embedpress_content = false;
1133 return false;
1134 }
1135
1136 $content = $post->post_content;
1137
1138 // Check for EmbedPress shortcodes
1139 if (has_shortcode($content, 'embedpress')) {
1140 self::$has_embedpress_content = true;
1141 return true;
1142 }
1143
1144 // Check for EmbedPress Gutenberg blocks
1145 $embedpress_blocks = [
1146 'embedpress/embedpress',
1147 'embedpress/google-docs-block',
1148 'embedpress/google-sheets-block',
1149 'embedpress/google-slides-block',
1150 'embedpress/google-forms-block',
1151 'embedpress/google-drawings-block',
1152 'embedpress/google-maps-block',
1153 'embedpress/youtube-block',
1154 'embedpress/vimeo-block',
1155 'embedpress/wistia-block',
1156 'embedpress/twitch-block',
1157 'embedpress/embedpress-pdf',
1158 'embedpress/document',
1159 'embedpress/embedpress-calendar'
1160 ];
1161
1162 foreach ($embedpress_blocks as $block_name) {
1163 if (has_block($block_name, $post)) {
1164 self::$has_embedpress_content = true;
1165 return true;
1166 }
1167 }
1168
1169 // Check for Elementor EmbedPress widgets
1170 if (class_exists('\Elementor\Plugin')) {
1171 $document = \Elementor\Plugin::$instance->documents->get($post->ID);
1172 if ($document && method_exists($document, 'is_built_with_elementor') && $document->is_built_with_elementor()) {
1173 $elementor_data = get_post_meta($post->ID, '_elementor_data', true);
1174 if ($elementor_data && is_string($elementor_data) && (strpos($elementor_data, 'embedpress') !== false || strpos($elementor_data, 'Embedpress') !== false)) {
1175 self::$has_embedpress_content = true;
1176 return true;
1177 }
1178 }
1179 }
1180
1181 self::$has_embedpress_content = false;
1182 return false;
1183 }
1184
1185 /**
1186 * Check if any of the required providers match the detected embed types
1187 *
1188 * @param array $required_providers List of providers this asset needs
1189 * @return bool
1190 */
1191 private static function check_provider_match($required_providers)
1192 {
1193 // In Elementor editor, always load provider scripts to allow live preview
1194 // because we can't detect unsaved widgets from _elementor_data
1195 if (class_exists('\Elementor\Plugin')) {
1196 $elementor = \Elementor\Plugin::$instance;
1197 if (isset($elementor->editor) && $elementor->editor->is_edit_mode()) {
1198 return true;
1199 }
1200 }
1201
1202 $detected_types = self::detect_embed_types();
1203
1204 // If no embeds detected, don't load
1205 if (empty($detected_types)) {
1206 return false;
1207 }
1208
1209 // Check if any required provider matches detected types
1210 foreach ($required_providers as $provider) {
1211 if (in_array($provider, $detected_types)) {
1212 return true;
1213 }
1214 }
1215
1216 return false;
1217 }
1218
1219 /**
1220 * Detect all embed types on the current page
1221 *
1222 * @return array List of detected embed types
1223 */
1224 private static function detect_embed_types()
1225 {
1226 // Cache the result to avoid multiple checks
1227 if (self::$detected_embed_types !== null) {
1228 return self::$detected_embed_types;
1229 }
1230
1231 self::$detected_embed_types = [];
1232
1233 global $post;
1234
1235 if (!$post) {
1236 return self::$detected_embed_types;
1237 }
1238
1239 $content = $post->post_content;
1240
1241 // Detect from Gutenberg blocks
1242 if (function_exists('has_blocks') && has_blocks($content)) {
1243 $blocks = parse_blocks($content);
1244 self::$detected_embed_types = array_merge(
1245 self::$detected_embed_types,
1246 self::detect_types_from_blocks($blocks)
1247 );
1248 }
1249
1250 // Detect from shortcodes
1251 self::$detected_embed_types = array_merge(
1252 self::$detected_embed_types,
1253 self::detect_types_from_shortcodes($content)
1254 );
1255
1256 // Detect from Elementor
1257 if (class_exists('\Elementor\Plugin')) {
1258 $document = \Elementor\Plugin::$instance->documents->get($post->ID);
1259 if ($document && method_exists($document, 'is_built_with_elementor') && $document->is_built_with_elementor()) {
1260 self::$detected_embed_types = array_merge(
1261 self::$detected_embed_types,
1262 self::detect_types_from_elementor($post->ID)
1263 );
1264 }
1265 }
1266
1267 // Remove duplicates
1268 self::$detected_embed_types = array_unique(self::$detected_embed_types);
1269
1270 return self::$detected_embed_types;
1271 }
1272
1273 /**
1274 * Detect embed types from Gutenberg blocks
1275 *
1276 * @param array $blocks
1277 * @return array
1278 */
1279 private static function detect_types_from_blocks($blocks)
1280 {
1281 $types = [];
1282
1283 foreach ($blocks as $block) {
1284 // Map block names to embed types
1285 $block_name = $block['blockName'] ?? '';
1286
1287 if ($block_name && strpos($block_name, 'embedpress/') === 0) {
1288 // Extract type from block name
1289 if ($block_name === 'embedpress/embedpress') {
1290 // Generic block - detect from URL
1291 $url = $block['attrs']['url'] ?? '';
1292 $types = array_merge($types, self::detect_type_from_url($url));
1293 } elseif ($block_name === 'embedpress/embedpress-pdf') {
1294 $types[] = 'pdf';
1295 } elseif ($block_name === 'embedpress/document') {
1296 $types[] = 'document';
1297 } elseif ($block_name === 'embedpress/youtube-block') {
1298 $types[] = 'youtube';
1299 } elseif ($block_name === 'embedpress/vimeo-block') {
1300 $types[] = 'vimeo';
1301 } elseif ($block_name === 'embedpress/google-docs-block') {
1302 $types[] = 'google-docs';
1303 } elseif ($block_name === 'embedpress/google-sheets-block') {
1304 $types[] = 'google-sheets';
1305 } elseif ($block_name === 'embedpress/google-slides-block') {
1306 $types[] = 'google-slides';
1307 } elseif ($block_name === 'embedpress/wistia-block') {
1308 $types[] = 'wistia';
1309 } elseif ($block_name === 'embedpress/twitch-block') {
1310 $types[] = 'twitch';
1311 }
1312 }
1313
1314 // Recursively check inner blocks
1315 if (!empty($block['innerBlocks'])) {
1316 $types = array_merge($types, self::detect_types_from_blocks($block['innerBlocks']));
1317 }
1318 }
1319
1320 return $types;
1321 }
1322
1323 /**
1324 * Detect embed types from shortcodes
1325 *
1326 * @param string $content
1327 * @return array
1328 */
1329 private static function detect_types_from_shortcodes($content)
1330 {
1331 $types = [];
1332
1333 if (!is_string($content)) {
1334 return $types;
1335 }
1336
1337 // Find all embedpress shortcodes with URL attribute (with or without quotes)
1338 // Matches: [embedpress url="..."], [embedpress url='...'], [embedpress url=...]
1339 if (preg_match_all('/\[embedpress[^\]]*url=["\']?([^"\'\s\]]+)["\']?[^\]]*\]/i', $content, $matches)) {
1340 foreach ($matches[1] as $url) {
1341 $types = array_merge($types, self::detect_type_from_url($url));
1342 }
1343 }
1344
1345 // Find embedpress shortcodes with URL between tags
1346 // Matches: [embedpress]URL[/embedpress]
1347 if (preg_match_all('/\[embedpress[^\]]*\]([^\[]+)\[\/embedpress\]/i', $content, $matches)) {
1348 foreach ($matches[1] as $url) {
1349 $url = trim($url);
1350 if (!empty($url)) {
1351 $types = array_merge($types, self::detect_type_from_url($url));
1352 }
1353 }
1354 }
1355
1356 return $types;
1357 }
1358
1359 /**
1360 * Detect embed types from Elementor
1361 *
1362 * @param int $post_id
1363 * @return array
1364 */
1365 private static function detect_types_from_elementor($post_id)
1366 {
1367 $types = [];
1368 $elementor_data = get_post_meta($post_id, '_elementor_data', true);
1369
1370 if (!$elementor_data || !is_string($elementor_data)) {
1371 return $types;
1372 }
1373
1374 // Decode JSON data
1375 $data = json_decode($elementor_data, true);
1376 if (!$data || !is_array($data)) {
1377 return $types;
1378 }
1379
1380 // Recursively search for EmbedPress widgets
1381 $types = self::detect_types_from_elementor_data($data);
1382
1383 return $types;
1384 }
1385
1386 /**
1387 * Recursively detect types from Elementor data
1388 *
1389 * @param array $data
1390 * @return array
1391 */
1392 private static function detect_types_from_elementor_data($data)
1393 {
1394 $types = [];
1395
1396 if (!is_array($data)) {
1397 return $types;
1398 }
1399
1400 foreach ($data as $element) {
1401 if (!is_array($element)) {
1402 continue;
1403 }
1404
1405 // Check if this is an EmbedPress widget
1406 $widget_type = $element['widgetType'] ?? '';
1407 if ($widget_type && (strpos($widget_type, 'embedpress') !== false || strpos($widget_type, 'Embedpress') !== false)) {
1408 // Get the embed source
1409 $settings = $element['settings'] ?? [];
1410 $source = $settings['embedpress_pro_embeded_source'] ?? '';
1411 $url = $settings['embedpress_embeded_link'] ?? '';
1412
1413 if ($source) {
1414 $types[] = $source;
1415 } elseif ($url) {
1416 $types = array_merge($types, self::detect_type_from_url($url));
1417 }
1418 }
1419
1420 // Recursively check elements
1421 if (isset($element['elements'])) {
1422 $types = array_merge($types, self::detect_types_from_elementor_data($element['elements']));
1423 }
1424 }
1425
1426 return $types;
1427 }
1428
1429 /**
1430 * Detect embed type from URL using Embera's provider detection
1431 *
1432 * @param string $url
1433 * @return array
1434 */
1435 private static function detect_type_from_url($url)
1436 {
1437 $types = [];
1438
1439 if (empty($url) || !is_string($url)) {
1440 return $types;
1441 }
1442
1443 // Use Helper class which leverages Embera's built-in provider detection
1444 if (class_exists('\EmbedPress\Includes\Classes\Helper')) {
1445 $provider_name = Helper::get_provider_name($url);
1446
1447 if (!empty($provider_name)) {
1448 // Normalize provider name to lowercase for consistency
1449 $provider_name = strtolower($provider_name);
1450
1451 // Map provider names to asset provider keys
1452 $provider_map = [
1453 'youtube' => 'youtube',
1454 'youtubechannel' => 'youtube-channel',
1455 'vimeo' => 'vimeo',
1456 'instagram' => 'instagram',
1457 'instagramfeed' => 'instagram',
1458 'opensea' => 'opensea',
1459 'wistia' => 'wistia',
1460 'twitch' => 'twitch',
1461 'meetup' => 'meetup',
1462 'googledocs' => 'google-docs',
1463 'googlesheets' => 'google-sheets',
1464 'googleslides' => 'google-slides',
1465 ];
1466
1467 // Check if provider name matches our map
1468 if (isset($provider_map[$provider_name])) {
1469 $types[] = $provider_map[$provider_name];
1470 return $types;
1471 }
1472
1473 // Check for document types from Helper's response
1474 if (strpos($provider_name, 'document_') === 0) {
1475 $types[] = 'document';
1476 return $types;
1477 }
1478 }
1479 }
1480
1481 // Fallback to manual detection for special cases not handled by Embera
1482 $url_lower = strtolower($url);
1483
1484 // YouTube special cases (channel, live, shorts)
1485 if (strpos($url_lower, 'youtube.com') !== false || strpos($url_lower, 'youtu.be') !== false) {
1486 if (strpos($url_lower, '/channel/') !== false || strpos($url_lower, '/c/') !== false || strpos($url_lower, '/@') !== false) {
1487 $types[] = 'youtube-channel';
1488 } elseif (strpos($url_lower, '/live') !== false) {
1489 $types[] = 'youtube-live';
1490 } elseif (strpos($url_lower, '/shorts/') !== false) {
1491 $types[] = 'youtube-shorts';
1492 } else {
1493 $types[] = 'youtube';
1494 }
1495 }
1496 // PDF detection
1497 elseif (preg_match('/\.pdf$/i', $url)) {
1498 $types[] = 'pdf';
1499 }
1500 // Document detection
1501 elseif (preg_match('/\.(doc|docx|ppt|pptx|xls|xlsx)$/i', $url)) {
1502 $types[] = 'document';
1503 }
1504 // Self-hosted video
1505 elseif (preg_match('/\.(mp4|mov|avi|wmv|flv|mkv|webm|mpeg|mpg)$/i', $url)) {
1506 $types[] = 'video';
1507 }
1508 // Self-hosted audio
1509 elseif (preg_match('/\.(mp3|wav|ogg|aac)$/i', $url)) {
1510 $types[] = 'audio';
1511 }
1512
1513 return $types;
1514 }
1515 }
1516