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