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