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