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