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