PluginProbe ʕ •ᴥ•ʔ
EmbedPress – PDF Embedder, Embed PDF viewer, YouTube Videos, 3D FlipBook, Social feeds & more / 4.4.11
EmbedPress – PDF Embedder, Embed PDF viewer, YouTube Videos, 3D FlipBook, Social feeds & more v4.4.11
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 3 months ago LocalizationManager.php 5 months ago init.php 9 months ago
AssetManager.php
1541 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',
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',
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'],
271 ],
272 'meetup-timezone-js' => [
273 'file' => 'js/meetup-timezone.js',
274 'deps' => [],
275 'contexts' => ['frontend', 'elementor'],
276 'type' => 'script',
277 'footer' => true,
278 'handle' => 'embedpress-meetup-timezone',
279 'priority' => 15,
280 'providers' => ['meetup'], // Only for Meetup embeds
281 ],
282 'gutenberg-script-js' => [
283 'file' => 'js/gutneberg-script.js',
284 'deps' => ['wp-blocks', 'wp-element'],
285 'contexts' => ['editor'],
286 'type' => 'script',
287 'footer' => true,
288 'handle' => 'embedpress-gutenberg-script',
289 'priority' => 15,
290 ],
291 'init-plyr-js' => [
292 'file' => 'js/initplyr.js',
293 'deps' => ['jquery', 'embedpress-plyr'],
294 'contexts' => ['frontend', 'elementor'],
295 'type' => 'script',
296 'footer' => true,
297 'handle' => 'embedpress-init-plyr',
298 'priority' => 15,
299 'condition' => 'custom_player', // Only load if custom player is enabled
300 'providers' => ['youtube', 'vimeo', 'video', 'audio'], // Only for these providers
301 ],
302 'instafeed-js' => [
303 'file' => 'js/instafeed.js',
304 'deps' => ['jquery'],
305 'contexts' => ['frontend', 'elementor'],
306 'type' => 'script',
307 'footer' => true,
308 'handle' => 'embedpress-instafeed',
309 'priority' => 15,
310 'providers' => ['instagram'], // Only for Instagram embeds
311 ],
312 'license-js' => [
313 'file' => 'js/license.js',
314 'deps' => ['jquery', 'wp-url'],
315 'contexts' => ['admin'],
316 'type' => 'script',
317 'footer' => true,
318 'handle' => 'embedpress-license',
319 'priority' => 15,
320 'page' => 'embedpress'
321 ],
322 'feature-notices-js' => [
323 'file' => 'js/feature-notices.js',
324 'deps' => ['jquery'],
325 'contexts' => ['admin'],
326 'type' => 'script',
327 'footer' => true,
328 'handle' => 'embedpress-feature-notices',
329 'priority' => 15,
330 // 'page' => 'embedpress'
331 ],
332 'preview-js' => [
333 'file' => 'js/preview.js',
334 'deps' => ['jquery'],
335 'contexts' => ['classic_editor'],
336 'type' => 'script',
337 'footer' => true,
338 'handle' => 'embedpress-preview',
339 'priority' => 15,
340 ],
341 'settings-js' => [
342 'file' => 'js/settings.js',
343 'deps' => ['jquery', 'wp-color-picker'],
344 'contexts' => ['admin'],
345 'type' => 'script',
346 'footer' => true,
347 'handle' => 'embedpress-settings',
348 'priority' => 15,
349 'page' => 'embedpress'
350 ],
351
352 // 🎨 Styles (Ordered by Priority)
353 // ----------------------------------
354
355 // Build CSS files (analytics.build.css is handled by Analytics.php)
356
357 // Legacy CSS files
358 'admin-notices-css' => [
359 'file' => 'css/admin-notices.css',
360 'deps' => [],
361 'contexts' => ['admin'],
362 'type' => 'style',
363 'handle' => 'embedpress-admin-notices',
364 'priority' => 5,
365 // 'page' => 'embedpress'
366 ],
367 'feature-notices-css' => [
368 'file' => 'css/feature-notices.css',
369 'deps' => [],
370 'contexts' => ['admin'],
371 'type' => 'style',
372 'handle' => 'embedpress-feature-notices',
373 'priority' => 5,
374 ],
375
376 'el-icon-css' => [
377 'file' => 'css/el-icon.css',
378 'deps' => [],
379 'contexts' => ['elementor-editor'],
380 'type' => 'style',
381 'handle' => 'embedpress-el-icon',
382 'priority' => 5,
383 ],
384 'embedpress-elementor-css' => [
385 'file' => 'css/embedpress-elementor.css',
386 'deps' => [],
387 'contexts' => ['elementor'],
388 'type' => 'style',
389 'handle' => 'embedpress-elementor-css',
390 'priority' => 5,
391 ],
392 'embedpress-css' => [
393 'file' => 'css/embedpress.css',
394 'deps' => [],
395 'contexts' => ['editor', 'frontend', 'elementor'],
396 'type' => 'style',
397 'handle' => 'embedpress-css',
398 'priority' => 5,
399 ],
400 'modal-css' => [
401 'file' => 'css/modal.css',
402 'deps' => [],
403 'contexts' => ['editor', 'classic_editor'],
404 'type' => 'style',
405 'handle' => 'embedpress-classic-editor-modal',
406 'priority' => 6,
407 ],
408 'meetup-events-css' => [
409 'file' => 'css/meetup-events.css',
410 'deps' => ['embedpress-css'],
411 'contexts' => ['frontend', 'editor', 'elementor'],
412 'type' => 'style',
413 'handle' => 'embedpress-meetup-events',
414 'providers' => ['meetup'], // Only for Meetup embeds
415 'priority' => 6,
416 ],
417 'settings-icons-css' => [
418 'file' => 'css/settings-icons.css',
419 'deps' => [],
420 'contexts' => ['admin'],
421 'type' => 'style',
422 'handle' => 'embedpress-settings-icons',
423 'priority' => 5,
424 'page' => 'embedpress'
425 ],
426 'settings-css' => [
427 'file' => 'css/settings.css',
428 'deps' => [],
429 'contexts' => ['admin'],
430 'type' => 'style',
431 'handle' => 'embedpress-settings-css',
432 'priority' => 5,
433 'page' => 'embedpress'
434 ],
435 'admin-css' => [
436 'file' => 'css/admin.css',
437 'deps' => [],
438 'contexts' => ['admin'],
439 'type' => 'style',
440 'handle' => 'embedpress-admin-css',
441 'priority' => 5,
442 'page' => 'embedpress'
443 ],
444 ];
445
446 /**
447 * Initialize asset manager
448 */
449 public static function init()
450 {
451 // Register all assets early so they're available as dependencies for Elementor and other plugins
452 add_action('wp_enqueue_scripts', [__CLASS__, 'register_all_assets'], 1);
453 add_action('admin_enqueue_scripts', [__CLASS__, 'register_all_assets'], 1);
454
455 // Use proper priorities to ensure correct load order
456 add_action('wp_enqueue_scripts', [__CLASS__, 'enqueue_frontend_assets'], 5);
457 add_action('admin_enqueue_scripts', [__CLASS__, 'enqueue_admin_assets'], 5);
458 add_action('admin_enqueue_scripts', [__CLASS__, 'enqueue_classic_editor_assets'], 5);
459 add_action('enqueue_block_assets', [__CLASS__, 'enqueue_block_assets'], 5);
460
461
462 add_action('enqueue_block_editor_assets', [__CLASS__, 'enqueue_editor_assets'], 5);
463
464 // Elementor preview (frontend iframe) - enqueue after scripts are enqueued
465 add_action('elementor/frontend/after_enqueue_scripts', [__CLASS__, 'enqueue_elementor_assets'], 5);
466
467 // In Elementor editor, WP_Scripts registry is reset; re-register our assets before enqueuing
468 add_action('elementor/editor/before_enqueue_scripts', [__CLASS__, 'register_all_assets'], 1);
469 // Elementor editor panel (admin) - enqueue after editor scripts are enqueued
470 add_action('elementor/editor/after_enqueue_scripts', [__CLASS__, 'enqueue_elementor_editor_assets'], 5);
471 }
472
473 /**
474 * Register all assets early so they're available as dependencies
475 * This is crucial for Elementor widgets that declare script/style dependencies
476 */
477 public static function register_all_assets()
478 {
479 foreach (self::$assets as $key => $asset) {
480 $file_url = EMBEDPRESS_PLUGIN_DIR_URL . 'assets/' . $asset['file'];
481 $file_path = EMBEDPRESS_PLUGIN_DIR_PATH . '/assets/' . $asset['file'];
482
483 if (!file_exists($file_path)) {
484 continue;
485 }
486
487 $version = filemtime($file_path);
488
489 // Register (not enqueue) all assets
490 if ($asset['type'] === 'script') {
491 wp_register_script(
492 $asset['handle'],
493 $file_url,
494 $asset['deps'],
495 $version,
496 !empty($asset['footer'])
497 );
498
499 // Add module attribute for ES modules (only build files)
500 if (strpos($asset['file'], '.build.js') !== false) {
501 // Track this handle as a module
502 self::$module_handles[] = $asset['handle'];
503
504 // Add the global filter only once
505 if (!self::$module_filter_added) {
506 self::$module_filter_added = true;
507 add_filter('script_loader_tag', [__CLASS__, 'add_module_attribute'], 10, 2);
508 }
509 }
510 } elseif ($asset['type'] === 'style') {
511 wp_register_style(
512 $asset['handle'],
513 $file_url,
514 $asset['deps'],
515 $version,
516 $asset['media'] ?? 'all'
517 );
518 }
519 }
520 }
521
522 /**
523 * Enqueue frontend assets
524 */
525 public static function enqueue_frontend_assets()
526 {
527 self::enqueue_assets_for_context('frontend');
528
529 // Setup frontend localization
530 LocalizationManager::setup_frontend_localization();
531 }
532
533 /**
534 * Enqueue admin assets
535 */
536 public static function enqueue_admin_assets($hook = '')
537 {
538 self::enqueue_assets_for_context('admin', $hook);
539
540 // Load settings assets only on EmbedPress settings pages
541 if (strpos($hook, 'embedpress') !== false) {
542 self::enqueue_assets_for_context('settings', $hook);
543
544 // Ensure wp-color-picker is loaded for settings page
545 wp_enqueue_style('wp-color-picker');
546
547 // Ensure media scripts are loaded
548 if (!did_action('wp_enqueue_media')) {
549 wp_enqueue_media();
550 }
551 }
552
553 // Setup admin localization
554 LocalizationManager::setup_admin_localization($hook);
555 }
556
557 /**
558 * Enqueue block assets (both editor and frontend)
559 */
560 public static function enqueue_block_assets()
561 {
562 // This runs on both frontend and editor for blocks
563 // For frontend, we don't need the editor scripts
564 if (is_admin()) {
565 self::enqueue_assets_for_context('editor');
566 }
567 }
568
569 /**
570 * Enqueue editor-only assets
571 */
572 public static function enqueue_editor_assets()
573 {
574 // Ensure editor assets are loaded
575 self::enqueue_assets_for_context('editor');
576
577 // Setup editor localization
578 LocalizationManager::setup_editor_localization();
579 }
580
581 public static function enqueue_classic_editor_assets()
582 {
583
584 // Ensure editor assets are loaded
585 self::enqueue_assets_for_context('classic_editor');
586
587 // Setup editor localization
588 LocalizationManager::setup_editor_localization();
589 }
590
591 /**
592 * Enqueue Elementor frontend assets
593 */
594 public static function enqueue_elementor_assets()
595 {
596 self::enqueue_assets_for_context('elementor');
597
598 // Setup Elementor localization
599 LocalizationManager::setup_elementor_localization();
600 }
601
602 /**
603 * Enqueue Elementor editor assets
604 */
605 public static function enqueue_elementor_editor_assets()
606 {
607 // In Elementor editor, load only elementor and elementor-editor contexts
608 // Do NOT load 'editor' context - that's for Gutenberg only
609 self::enqueue_assets_for_context('elementor');
610 self::enqueue_assets_for_context('elementor-editor');
611
612 // Setup Elementor editor localization
613 LocalizationManager::setup_elementor_localization();
614 }
615
616 /**
617 * Enqueue assets for a specific context
618 */
619 private static function enqueue_assets_for_context($context, $hook = '')
620 {
621
622 $assets_to_enqueue = [];
623
624 // Collect assets for this context
625 foreach (self::$assets as $key => $asset) {
626 if (in_array($context, $asset['contexts'])) {
627 // Check if asset has page restriction
628 if (isset($asset['page']) && !empty($asset['page'])) {
629 // Only enqueue if we're on the specified page
630 if (strpos($hook, $asset['page']) !== false) {
631 $assets_to_enqueue[] = array_merge($asset, ['key' => $key]);
632 }
633 // If page doesn't match, don't enqueue this asset
634 } else {
635 // No page restriction, enqueue normally
636 $assets_to_enqueue[] = array_merge($asset, ['key' => $key]);
637 }
638 }
639 }
640
641 // Sort by priority
642 usort($assets_to_enqueue, function ($a, $b) {
643 return $a['priority'] - $b['priority'];
644 });
645
646 // Enqueue assets
647 foreach ($assets_to_enqueue as $asset) {
648 self::enqueue_single_asset($asset);
649 }
650 }
651
652 /**
653 * Enqueue a single asset (assumes asset is already registered)
654 */
655 private static function enqueue_single_asset($asset)
656 {
657 $file_path = EMBEDPRESS_PLUGIN_DIR_PATH . '/assets/' . $asset['file'];
658
659 if (! file_exists($file_path)) {
660 return;
661 }
662
663 // Check if we should load this asset based on current context
664 if (!self::should_load_asset($asset)) {
665 return;
666 }
667
668 // Enqueue the already-registered asset
669 if ($asset['type'] === 'script') {
670 wp_enqueue_script($asset['handle']);
671 } elseif ($asset['type'] === 'style') {
672 wp_enqueue_style($asset['handle']);
673 }
674 }
675
676 /**
677 * Determine if an asset should be loaded based on current context
678 */
679 private static function should_load_asset($asset)
680 {
681 // Check conditional loading requirements first
682 if (isset($asset['condition'])) {
683 if (!self::check_asset_condition($asset['condition'])) {
684 return false;
685 }
686 }
687
688 // Check provider-specific loading
689 if (isset($asset['providers']) && !empty($asset['providers'])) {
690 if (!self::check_provider_match($asset['providers'])) {
691 return false;
692 }
693 }
694
695 // Get current environment state
696 $is_admin = is_admin();
697 $is_elementor_editor = false;
698 $is_elementor_preview = false;
699 $is_gutenberg_editor = false;
700
701 // Check Elementor states
702 if (class_exists('\Elementor\Plugin')) {
703 $elementor = \Elementor\Plugin::$instance;
704
705 if (isset($elementor->editor)) {
706 $is_elementor_editor = $elementor->editor->is_edit_mode();
707 }
708
709 if (isset($elementor->preview)) {
710 $is_elementor_preview = $elementor->preview->is_preview_mode();
711 }
712 }
713
714 // Check if we're in Gutenberg editor
715 if ($is_admin) {
716 global $pagenow;
717 $is_gutenberg_editor = (
718 $pagenow === 'post.php' ||
719 $pagenow === 'post-new.php' ||
720 $pagenow === 'site-editor.php'
721 ) && function_exists('use_block_editor_for_post_type');
722
723 // Check if we're in classic editor (not Gutenberg)
724 $is_classic_editor = false;
725 if ($pagenow === 'post.php' || $pagenow === 'post-new.php') {
726 // Check if classic editor is being used
727 if (
728 isset($_GET['classic-editor']) ||
729 (function_exists('use_block_editor_for_post_type') &&
730 isset($_GET['post']) &&
731 !use_block_editor_for_post_type(get_post_type($_GET['post'])))
732 ) {
733 $is_classic_editor = true;
734 }
735 // Also check if Classic Editor plugin is active and set to classic mode
736 if (
737 class_exists('Classic_Editor') &&
738 get_option('classic-editor-replace') === 'classic'
739 ) {
740 $is_classic_editor = true;
741 }
742 }
743 }
744
745 // Asset loading logic based on contexts
746 foreach ($asset['contexts'] as $context) {
747
748
749 switch ($context) {
750 case 'frontend':
751 // Load on frontend (not in any editor or admin)
752 if (!$is_admin && !$is_elementor_editor && !$is_elementor_preview) {
753 return true;
754 }
755 break;
756
757 case 'admin':
758 // Load in WordPress admin (but not in Elementor editor)
759 if ($is_admin && !$is_elementor_editor && !$is_elementor_preview) {
760 // Check if asset has page restriction
761 if (isset($asset['page'])) {
762 return self::is_embedpress_admin_page($asset['page']);
763 }
764 return true;
765 }
766 break;
767
768 case 'editor':
769 // Load ONLY in Gutenberg editor (not in Elementor editor or other admin pages)
770 if ($is_gutenberg_editor && !$is_elementor_editor) {
771 return true;
772 }
773 break;
774 case 'classic_editor':
775 // Load only in classic editor (TinyMCE)
776 if ($is_classic_editor) {
777 return true;
778 }
779 break;
780 case 'elementor':
781 // Load in Elementor editor, preview, or frontend when Elementor is rendering
782 if ($is_elementor_editor || $is_elementor_preview) {
783 return true;
784 }
785 // Also load on frontend if Elementor content is present
786 if (!$is_admin && self::has_elementor_content()) {
787 return true;
788 }
789 break;
790
791 case 'elementor-editor':
792
793 // Load only in Elementor editor (not preview or frontend)
794 if ($is_elementor_editor) {
795 return true;
796 }
797 break;
798
799 case 'settings':
800 // Load only on EmbedPress settings pages
801 if ($is_admin && !$is_elementor_editor && !$is_elementor_preview) {
802 return true;
803 }
804 break;
805 }
806 }
807
808 // Check if this is an individual block script and if it should be loaded
809 if (strpos($asset['handle'], 'embedpress-block-') === 0) {
810 return self::should_load_individual_block($asset['handle']);
811 }
812
813 return false;
814 }
815
816 /**
817 * Check if we're on an EmbedPress admin page
818 */
819 private static function is_embedpress_admin_page($page_type)
820 {
821 global $pagenow;
822
823 // Get current page
824 $current_page = isset($_GET['page']) ? $_GET['page'] : '';
825
826 switch ($page_type) {
827 case 'embedpress':
828 // Check if we're on any EmbedPress admin page
829 return (
830 strpos($current_page, 'embedpress') !== false ||
831 $pagenow === 'admin.php' && strpos($current_page, 'embedpress') !== false
832 );
833 case 'embedpress-analytics':
834 return $current_page === 'embedpress-analytics';
835 default:
836 return false;
837 }
838 }
839
840 /**
841 * Check if individual block should be loaded based on active blocks
842 */
843 private static function should_load_individual_block($handle)
844 {
845 // Get active blocks from settings
846 $elements = (array) get_option(EMBEDPRESS_PLG_NAME . ":elements", []);
847 $active_blocks = isset($elements['gutenberg']) ? (array) $elements['gutenberg'] : [];
848
849 // Map handles to block names
850 $block_map = [
851 'embedpress-block-embedpress' => 'embedpress',
852 'embedpress-block-document' => 'document',
853 'embedpress-block-pdf' => 'embedpress-pdf',
854 'embedpress-block-calendar' => 'embedpress-calendar',
855 'embedpress-block-google-docs' => 'google-docs',
856 'embedpress-block-google-drawings' => 'google-drawings',
857 'embedpress-block-google-forms' => 'google-forms',
858 'embedpress-block-google-maps' => 'google-maps',
859 'embedpress-block-google-sheets' => 'google-sheets',
860 'embedpress-block-google-slides' => 'google-slides',
861 'embedpress-block-twitch' => 'twitch',
862 'embedpress-block-wistia' => 'wistia',
863 'embedpress-block-youtube' => 'youtube'
864 ];
865
866 $block_name = isset($block_map[$handle]) ? $block_map[$handle] : '';
867
868 // If no block name found or no active blocks set, load all blocks (default behavior)
869 if (empty($block_name) || empty($active_blocks)) {
870 return true;
871 }
872
873 // Check if this specific block is active
874 return in_array($block_name, $active_blocks);
875 }
876
877 /**
878 * Check if current page has Elementor content
879 */
880 private static function has_elementor_content()
881 {
882 if (! class_exists('\Elementor\Plugin')) {
883 return false;
884 }
885
886 if (is_singular()) {
887 $post_id = get_the_ID();
888 if (empty($post_id) || ! is_numeric($post_id)) {
889 return false;
890 }
891
892 $document = \Elementor\Plugin::$instance->documents->get($post_id);
893
894 if ($document && method_exists($document, 'is_built_with_elementor')) {
895 return (bool) $document->is_built_with_elementor();
896 }
897 }
898
899 return false;
900 }
901
902
903 /**
904 * Get asset URL
905 */
906 public static function get_asset_url($file)
907 {
908 return EMBEDPRESS_PLUGIN_DIR_URL . 'assets/' . $file;
909 }
910
911
912
913 /**
914 * Add module attribute to script tags for ES modules
915 */
916 public static function add_module_attribute($tag, $handle)
917 {
918 if (in_array($handle, self::$module_handles)) {
919 // Only add type="module" if it doesn't already exist
920 if (strpos($tag, 'type="module"') === false) {
921 return str_replace('<script ', '<script type="module" ', $tag);
922 }
923 }
924 return $tag;
925 }
926
927 /**
928 * Check if asset exists
929 */
930 public static function asset_exists($file)
931 {
932 $plugin_path = dirname(dirname(dirname(__DIR__)));
933 return file_exists($plugin_path . '/assets/' . $file);
934 }
935
936 /**
937 * Check if an asset condition is met
938 *
939 * @param string $condition The condition to check
940 * @return bool
941 */
942 private static function check_asset_condition($condition)
943 {
944 switch ($condition) {
945 case 'custom_player':
946 return self::is_custom_player_enabled();
947
948 case 'has_content':
949 // In Elementor editor, always load core scripts with has_content condition
950 // because we can't detect unsaved content
951 if (class_exists('\Elementor\Plugin')) {
952 $elementor = \Elementor\Plugin::$instance;
953 if (isset($elementor->editor) && $elementor->editor->is_edit_mode()) {
954 return true;
955 }
956 }
957 return self::has_embedpress_content();
958
959 case 'lazy_load':
960 // In Elementor editor, always load lazy load scripts
961 // because we can't detect unsaved content
962 if (class_exists('\Elementor\Plugin')) {
963 $elementor = \Elementor\Plugin::$instance;
964 if (isset($elementor->editor) && $elementor->editor->is_edit_mode()) {
965 return true;
966 }
967 }
968 return self::has_lazy_load_enabled();
969
970 case 'always':
971 default:
972 return true;
973 }
974 }
975
976 /**
977 * Check if custom player is enabled on the current page
978 *
979 * @return bool
980 */
981 private static function is_custom_player_enabled()
982 {
983 // Cache the result to avoid multiple checks
984 if (self::$custom_player_enabled !== null) {
985 return self::$custom_player_enabled;
986 }
987
988 // In Elementor editor, always load custom player scripts to allow live preview
989 // because we can't detect unsaved widget settings
990 if (class_exists('\Elementor\Plugin')) {
991 $elementor = \Elementor\Plugin::$instance;
992 if (isset($elementor->editor) && $elementor->editor->is_edit_mode()) {
993 self::$custom_player_enabled = true;
994 return true;
995 }
996 }
997
998 global $post;
999
1000 if (!$post) {
1001 self::$custom_player_enabled = false;
1002 return false;
1003 }
1004
1005 $content = $post->post_content;
1006
1007 // Check for custom player in Gutenberg blocks
1008 if (function_exists('has_blocks') && has_blocks($content)) {
1009 $blocks = parse_blocks($content);
1010 if (self::has_custom_player_in_blocks($blocks)) {
1011 self::$custom_player_enabled = true;
1012 return true;
1013 }
1014 }
1015
1016 // Check for custom player in Elementor
1017 if (class_exists('\Elementor\Plugin')) {
1018 $document = \Elementor\Plugin::$instance->documents->get($post->ID);
1019 if ($document && method_exists($document, 'is_built_with_elementor') && $document->is_built_with_elementor()) {
1020 // Check Elementor meta for custom player settings
1021 $elementor_data = get_post_meta($post->ID, '_elementor_data', true);
1022 if ($elementor_data && is_string($elementor_data) && (strpos($elementor_data, 'emberpress_custom_player') !== false || strpos($elementor_data, '"customPlayer":true') !== false)) {
1023 self::$custom_player_enabled = true;
1024 return true;
1025 }
1026 }
1027 }
1028
1029 // Check for custom player in shortcodes (look for customPlayer attribute)
1030 if (has_shortcode($content, 'embedpress')) {
1031 if (is_string($content) && (strpos($content, 'customPlayer') !== false || strpos($content, 'custom_player') !== false)) {
1032 self::$custom_player_enabled = true;
1033 return true;
1034 }
1035 }
1036
1037 self::$custom_player_enabled = false;
1038 return false;
1039 }
1040
1041 /**
1042 * Check if blocks contain custom player settings
1043 *
1044 * @param array $blocks
1045 * @return bool
1046 */
1047 private static function has_custom_player_in_blocks($blocks)
1048 {
1049 foreach ($blocks as $block) {
1050 // Check if this is an EmbedPress block with custom player enabled
1051 $block_name = $block['blockName'] ?? '';
1052 if ($block_name && strpos($block_name, 'embedpress/') === 0) {
1053 if (isset($block['attrs']['customPlayer']) && $block['attrs']['customPlayer']) {
1054 return true;
1055 }
1056 }
1057
1058 // Recursively check inner blocks
1059 if (!empty($block['innerBlocks'])) {
1060 if (self::has_custom_player_in_blocks($block['innerBlocks'])) {
1061 return true;
1062 }
1063 }
1064 }
1065
1066 return false;
1067 }
1068
1069 /**
1070 * Check if lazy loading is enabled in any embed on the page
1071 *
1072 * @return bool
1073 */
1074 private static function has_lazy_load_enabled()
1075 {
1076 // Check global lazy load setting first - if enabled globally,
1077 // load lazy-load assets whenever EmbedPress content is present
1078 $g_settings = get_option(EMBEDPRESS_PLG_NAME, []);
1079 if (isset($g_settings['g_lazyload']) && $g_settings['g_lazyload'] == 1) {
1080 return self::has_embedpress_content();
1081 }
1082
1083 global $post;
1084
1085 if (!$post) {
1086 return false;
1087 }
1088
1089 $content = $post->post_content;
1090
1091 // Check if post content contains lazy load attributes in blocks
1092 if (function_exists('has_blocks') && has_blocks($content)) {
1093 $blocks = parse_blocks($content);
1094 if (self::has_lazy_load_in_blocks($blocks)) {
1095 return true;
1096 }
1097 }
1098
1099 // Check for Elementor meta (if Elementor is active)
1100 if (class_exists('\Elementor\Plugin')) {
1101 $document = \Elementor\Plugin::$instance->documents->get($post->ID);
1102 if ($document && method_exists($document, 'is_built_with_elementor') && $document->is_built_with_elementor()) {
1103 $elementor_data = get_post_meta($post->ID, '_elementor_data', true);
1104 if ($elementor_data && is_string($elementor_data) && strpos($elementor_data, '"enable_lazy_load":"yes"') !== false) {
1105 return true;
1106 }
1107 }
1108 }
1109
1110 return false;
1111 }
1112
1113 /**
1114 * Check if blocks contain lazy load settings
1115 *
1116 * @param array $blocks
1117 * @return bool
1118 */
1119 private static function has_lazy_load_in_blocks($blocks)
1120 {
1121 foreach ($blocks as $block) {
1122 // Check if this is an EmbedPress block with lazy load enabled
1123 $block_name = $block['blockName'] ?? '';
1124 if ($block_name && strpos($block_name, 'embedpress/') === 0) {
1125 if (isset($block['attrs']['enableLazyLoad']) && $block['attrs']['enableLazyLoad']) {
1126 return true;
1127 }
1128 }
1129
1130 // Recursively check inner blocks
1131 if (!empty($block['innerBlocks'])) {
1132 if (self::has_lazy_load_in_blocks($block['innerBlocks'])) {
1133 return true;
1134 }
1135 }
1136 }
1137
1138 return false;
1139 }
1140
1141
1142 /**
1143 * Check if current page has EmbedPress content
1144 *
1145 * @return bool
1146 */
1147 private static function has_embedpress_content()
1148 {
1149 // Cache the result to avoid multiple checks
1150 if (self::$has_embedpress_content !== null) {
1151 return self::$has_embedpress_content;
1152 }
1153
1154 global $post;
1155
1156 if (!$post) {
1157 self::$has_embedpress_content = false;
1158 return false;
1159 }
1160
1161 $content = $post->post_content;
1162
1163 // Check for EmbedPress shortcodes
1164 if (has_shortcode($content, 'embedpress')) {
1165 self::$has_embedpress_content = true;
1166 return true;
1167 }
1168
1169 // Check for EmbedPress Gutenberg blocks
1170 $embedpress_blocks = [
1171 'embedpress/embedpress',
1172 'embedpress/google-docs-block',
1173 'embedpress/google-sheets-block',
1174 'embedpress/google-slides-block',
1175 'embedpress/google-forms-block',
1176 'embedpress/google-drawings-block',
1177 'embedpress/google-maps-block',
1178 'embedpress/youtube-block',
1179 'embedpress/vimeo-block',
1180 'embedpress/wistia-block',
1181 'embedpress/twitch-block',
1182 'embedpress/embedpress-pdf',
1183 'embedpress/document',
1184 'embedpress/embedpress-calendar'
1185 ];
1186
1187 foreach ($embedpress_blocks as $block_name) {
1188 if (has_block($block_name, $post)) {
1189 self::$has_embedpress_content = true;
1190 return true;
1191 }
1192 }
1193
1194 // Check for Elementor EmbedPress widgets
1195 if (class_exists('\Elementor\Plugin')) {
1196 $document = \Elementor\Plugin::$instance->documents->get($post->ID);
1197 if ($document && method_exists($document, 'is_built_with_elementor') && $document->is_built_with_elementor()) {
1198 $elementor_data = get_post_meta($post->ID, '_elementor_data', true);
1199 if ($elementor_data && is_string($elementor_data) && (strpos($elementor_data, 'embedpress') !== false || strpos($elementor_data, 'Embedpress') !== false)) {
1200 self::$has_embedpress_content = true;
1201 return true;
1202 }
1203 }
1204 }
1205
1206 self::$has_embedpress_content = false;
1207 return false;
1208 }
1209
1210 /**
1211 * Check if any of the required providers match the detected embed types
1212 *
1213 * @param array $required_providers List of providers this asset needs
1214 * @return bool
1215 */
1216 private static function check_provider_match($required_providers)
1217 {
1218 // In Elementor editor, always load provider scripts to allow live preview
1219 // because we can't detect unsaved widgets from _elementor_data
1220 if (class_exists('\Elementor\Plugin')) {
1221 $elementor = \Elementor\Plugin::$instance;
1222 if (isset($elementor->editor) && $elementor->editor->is_edit_mode()) {
1223 return true;
1224 }
1225 }
1226
1227 $detected_types = self::detect_embed_types();
1228
1229 // If no embeds detected, don't load
1230 if (empty($detected_types)) {
1231 return false;
1232 }
1233
1234 // Check if any required provider matches detected types
1235 foreach ($required_providers as $provider) {
1236 if (in_array($provider, $detected_types)) {
1237 return true;
1238 }
1239 }
1240
1241 return false;
1242 }
1243
1244 /**
1245 * Detect all embed types on the current page
1246 *
1247 * @return array List of detected embed types
1248 */
1249 private static function detect_embed_types()
1250 {
1251 // Cache the result to avoid multiple checks
1252 if (self::$detected_embed_types !== null) {
1253 return self::$detected_embed_types;
1254 }
1255
1256 self::$detected_embed_types = [];
1257
1258 global $post;
1259
1260 if (!$post) {
1261 return self::$detected_embed_types;
1262 }
1263
1264 $content = $post->post_content;
1265
1266 // Detect from Gutenberg blocks
1267 if (function_exists('has_blocks') && has_blocks($content)) {
1268 $blocks = parse_blocks($content);
1269 self::$detected_embed_types = array_merge(
1270 self::$detected_embed_types,
1271 self::detect_types_from_blocks($blocks)
1272 );
1273 }
1274
1275 // Detect from shortcodes
1276 self::$detected_embed_types = array_merge(
1277 self::$detected_embed_types,
1278 self::detect_types_from_shortcodes($content)
1279 );
1280
1281 // Detect from Elementor
1282 if (class_exists('\Elementor\Plugin')) {
1283 $document = \Elementor\Plugin::$instance->documents->get($post->ID);
1284 if ($document && method_exists($document, 'is_built_with_elementor') && $document->is_built_with_elementor()) {
1285 self::$detected_embed_types = array_merge(
1286 self::$detected_embed_types,
1287 self::detect_types_from_elementor($post->ID)
1288 );
1289 }
1290 }
1291
1292 // Remove duplicates
1293 self::$detected_embed_types = array_unique(self::$detected_embed_types);
1294
1295 return self::$detected_embed_types;
1296 }
1297
1298 /**
1299 * Detect embed types from Gutenberg blocks
1300 *
1301 * @param array $blocks
1302 * @return array
1303 */
1304 private static function detect_types_from_blocks($blocks)
1305 {
1306 $types = [];
1307
1308 foreach ($blocks as $block) {
1309 // Map block names to embed types
1310 $block_name = $block['blockName'] ?? '';
1311
1312 if ($block_name && strpos($block_name, 'embedpress/') === 0) {
1313 // Extract type from block name
1314 if ($block_name === 'embedpress/embedpress') {
1315 // Generic block - detect from URL
1316 $url = $block['attrs']['url'] ?? '';
1317 $types = array_merge($types, self::detect_type_from_url($url));
1318 } elseif ($block_name === 'embedpress/embedpress-pdf') {
1319 $types[] = 'pdf';
1320 } elseif ($block_name === 'embedpress/document') {
1321 $types[] = 'document';
1322 } elseif ($block_name === 'embedpress/youtube-block') {
1323 $types[] = 'youtube';
1324 } elseif ($block_name === 'embedpress/vimeo-block') {
1325 $types[] = 'vimeo';
1326 } elseif ($block_name === 'embedpress/google-docs-block') {
1327 $types[] = 'google-docs';
1328 } elseif ($block_name === 'embedpress/google-sheets-block') {
1329 $types[] = 'google-sheets';
1330 } elseif ($block_name === 'embedpress/google-slides-block') {
1331 $types[] = 'google-slides';
1332 } elseif ($block_name === 'embedpress/wistia-block') {
1333 $types[] = 'wistia';
1334 } elseif ($block_name === 'embedpress/twitch-block') {
1335 $types[] = 'twitch';
1336 }
1337 }
1338
1339 // Recursively check inner blocks
1340 if (!empty($block['innerBlocks'])) {
1341 $types = array_merge($types, self::detect_types_from_blocks($block['innerBlocks']));
1342 }
1343 }
1344
1345 return $types;
1346 }
1347
1348 /**
1349 * Detect embed types from shortcodes
1350 *
1351 * @param string $content
1352 * @return array
1353 */
1354 private static function detect_types_from_shortcodes($content)
1355 {
1356 $types = [];
1357
1358 if (!is_string($content)) {
1359 return $types;
1360 }
1361
1362 // Find all embedpress shortcodes with URL attribute (with or without quotes)
1363 // Matches: [embedpress url="..."], [embedpress url='...'], [embedpress url=...]
1364 if (preg_match_all('/\[embedpress[^\]]*url=["\']?([^"\'\s\]]+)["\']?[^\]]*\]/i', $content, $matches)) {
1365 foreach ($matches[1] as $url) {
1366 $types = array_merge($types, self::detect_type_from_url($url));
1367 }
1368 }
1369
1370 // Find embedpress shortcodes with URL between tags
1371 // Matches: [embedpress]URL[/embedpress]
1372 if (preg_match_all('/\[embedpress[^\]]*\]([^\[]+)\[\/embedpress\]/i', $content, $matches)) {
1373 foreach ($matches[1] as $url) {
1374 $url = trim($url);
1375 if (!empty($url)) {
1376 $types = array_merge($types, self::detect_type_from_url($url));
1377 }
1378 }
1379 }
1380
1381 return $types;
1382 }
1383
1384 /**
1385 * Detect embed types from Elementor
1386 *
1387 * @param int $post_id
1388 * @return array
1389 */
1390 private static function detect_types_from_elementor($post_id)
1391 {
1392 $types = [];
1393 $elementor_data = get_post_meta($post_id, '_elementor_data', true);
1394
1395 if (!$elementor_data || !is_string($elementor_data)) {
1396 return $types;
1397 }
1398
1399 // Decode JSON data
1400 $data = json_decode($elementor_data, true);
1401 if (!$data || !is_array($data)) {
1402 return $types;
1403 }
1404
1405 // Recursively search for EmbedPress widgets
1406 $types = self::detect_types_from_elementor_data($data);
1407
1408 return $types;
1409 }
1410
1411 /**
1412 * Recursively detect types from Elementor data
1413 *
1414 * @param array $data
1415 * @return array
1416 */
1417 private static function detect_types_from_elementor_data($data)
1418 {
1419 $types = [];
1420
1421 if (!is_array($data)) {
1422 return $types;
1423 }
1424
1425 foreach ($data as $element) {
1426 if (!is_array($element)) {
1427 continue;
1428 }
1429
1430 // Check if this is an EmbedPress widget
1431 $widget_type = $element['widgetType'] ?? '';
1432 if ($widget_type && (strpos($widget_type, 'embedpress') !== false || strpos($widget_type, 'Embedpress') !== false)) {
1433 // Get the embed source
1434 $settings = $element['settings'] ?? [];
1435 $source = $settings['embedpress_pro_embeded_source'] ?? '';
1436 $url = $settings['embedpress_embeded_link'] ?? '';
1437
1438 if ($source) {
1439 $types[] = $source;
1440 } elseif ($url) {
1441 $types = array_merge($types, self::detect_type_from_url($url));
1442 }
1443 }
1444
1445 // Recursively check elements
1446 if (isset($element['elements'])) {
1447 $types = array_merge($types, self::detect_types_from_elementor_data($element['elements']));
1448 }
1449 }
1450
1451 return $types;
1452 }
1453
1454 /**
1455 * Detect embed type from URL using Embera's provider detection
1456 *
1457 * @param string $url
1458 * @return array
1459 */
1460 private static function detect_type_from_url($url)
1461 {
1462 $types = [];
1463
1464 if (empty($url) || !is_string($url)) {
1465 return $types;
1466 }
1467
1468 // Use Helper class which leverages Embera's built-in provider detection
1469 if (class_exists('\EmbedPress\Includes\Classes\Helper')) {
1470 $provider_name = Helper::get_provider_name($url);
1471
1472 if (!empty($provider_name)) {
1473 // Normalize provider name to lowercase for consistency
1474 $provider_name = strtolower($provider_name);
1475
1476 // Map provider names to asset provider keys
1477 $provider_map = [
1478 'youtube' => 'youtube',
1479 'youtubechannel' => 'youtube-channel',
1480 'vimeo' => 'vimeo',
1481 'instagram' => 'instagram',
1482 'instagramfeed' => 'instagram',
1483 'opensea' => 'opensea',
1484 'wistia' => 'wistia',
1485 'twitch' => 'twitch',
1486 'meetup' => 'meetup',
1487 'googledocs' => 'google-docs',
1488 'googlesheets' => 'google-sheets',
1489 'googleslides' => 'google-slides',
1490 ];
1491
1492 // Check if provider name matches our map
1493 if (isset($provider_map[$provider_name])) {
1494 $types[] = $provider_map[$provider_name];
1495 return $types;
1496 }
1497
1498 // Check for document types from Helper's response
1499 if (strpos($provider_name, 'document_') === 0) {
1500 $types[] = 'document';
1501 return $types;
1502 }
1503 }
1504 }
1505
1506 // Fallback to manual detection for special cases not handled by Embera
1507 $url_lower = strtolower($url);
1508
1509 // YouTube special cases (channel, live, shorts)
1510 if (strpos($url_lower, 'youtube.com') !== false || strpos($url_lower, 'youtu.be') !== false) {
1511 if (strpos($url_lower, '/channel/') !== false || strpos($url_lower, '/c/') !== false || strpos($url_lower, '/@') !== false) {
1512 $types[] = 'youtube-channel';
1513 } elseif (strpos($url_lower, '/live') !== false) {
1514 $types[] = 'youtube-live';
1515 } elseif (strpos($url_lower, '/shorts/') !== false) {
1516 $types[] = 'youtube-shorts';
1517 } else {
1518 $types[] = 'youtube';
1519 }
1520 }
1521 // PDF detection
1522 elseif (preg_match('/\.pdf$/i', $url)) {
1523 $types[] = 'pdf';
1524 }
1525 // Document detection
1526 elseif (preg_match('/\.(doc|docx|ppt|pptx|xls|xlsx)$/i', $url)) {
1527 $types[] = 'document';
1528 }
1529 // Self-hosted video
1530 elseif (preg_match('/\.(mp4|mov|avi|wmv|flv|mkv|webm|mpeg|mpg)$/i', $url)) {
1531 $types[] = 'video';
1532 }
1533 // Self-hosted audio
1534 elseif (preg_match('/\.(mp3|wav|ogg|aac)$/i', $url)) {
1535 $types[] = 'audio';
1536 }
1537
1538 return $types;
1539 }
1540 }
1541