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