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