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