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 / EmbedPress / Gutenberg / BlockManager.php
embedpress / EmbedPress / Gutenberg Last commit date
BlockManager.php 4 days ago EmbedPressBlockRenderer.php 4 days ago FallbackHandler.php 9 months ago InitBlocks.php 9 months ago
BlockManager.php
2017 lines
1 <?php
2
3 namespace EmbedPress\Gutenberg;
4
5 use EmbedPress\Includes\Classes\Helper;
6 use EmbedPress\Core\AssetManager;
7
8 /**
9 * Block Manager for EmbedPress
10 *
11 * Handles registration and management of all EmbedPress Gutenberg blocks
12 * using the new centralized structure.
13 */
14 class BlockManager
15 {
16
17 /**
18 * Instance of this class
19 */
20 private static $instance = null;
21
22 /**
23 * Blocks directory path
24 */
25 private $blocks_path;
26
27 /**
28 * Blocks directory URL
29 */
30 private $blocks_url;
31
32 /**
33 * Available blocks
34 */
35 private $available_blocks = [
36 'EmbedPress' => [
37 'name' => 'embedpress/embedpress',
38 'render_callback' => [EmbedPressBlockRenderer::class, 'render'],
39 'setting_key' => 'embedpress',
40 'supports_save_function' => true
41 ],
42 'embedpress-pdf' => [
43 'name' => 'embedpress/embedpress-pdf',
44 'render_callback' => [EmbedPressBlockRenderer::class, 'render_embedpress_pdf'],
45 'setting_key' => 'embedpress-pdf',
46 'supports_save_function' => true
47 ],
48 'document' => [
49 'name' => 'embedpress/document',
50 'render_callback' => [EmbedPressBlockRenderer::class, 'render_document'],
51 'setting_key' => 'document',
52 'supports_save_function' => true
53 ],
54 'embedpress-calendar' => [
55 'name' => 'embedpress/embedpress-calendar',
56 'render_callback' => [EmbedPressBlockRenderer::class, 'render_embedpress_calendar'],
57 'setting_key' => 'embedpress-calendar',
58 'supports_save_function' => true
59 ],
60 'youtube-block' => [
61 'name' => 'embedpress/youtube-block',
62 'render_callback' => [EmbedPressBlockRenderer::class, 'render_youtube_block'],
63 'setting_key' => 'youtube-block',
64 'supports_save_function' => false
65 ],
66 'wistia-block' => [
67 'name' => 'embedpress/wistia-block',
68 'render_callback' => [EmbedPressBlockRenderer::class, 'render_wistia_block'],
69 'setting_key' => 'wistia-block',
70 'supports_save_function' => false
71 ],
72 'pdf-gallery' => [
73 'name' => 'embedpress/pdf-gallery',
74 'render_callback' => [EmbedPressBlockRenderer::class, 'render_pdf_gallery'],
75 'setting_key' => 'pdf-gallery',
76 'supports_save_function' => true
77 ],
78 'google-reviews' => [
79 'name' => 'embedpress/google-reviews',
80 'render_callback' => [EmbedPressBlockRenderer::class, 'render_google_reviews'],
81 'setting_key' => 'google-reviews',
82 'supports_save_function' => false
83 ],
84 ];
85
86 /**
87 * Get instance
88 */
89 public static function get_instance()
90 {
91 if (null === self::$instance) {
92 self::$instance = new self();
93 }
94 return self::$instance;
95 }
96
97 /**
98 * Constructor
99 */
100 private function __construct()
101 {
102 // Use src directory if it exists (development), otherwise use built assets (production/distribution)
103 if (file_exists(EMBEDPRESS_PATH_BASE . 'src/Blocks/')) {
104 $this->blocks_path = EMBEDPRESS_PATH_BASE . 'src/Blocks/';
105 $this->blocks_url = EMBEDPRESS_URL_STATIC . '../src/Blocks/';
106 } else {
107 // Fallback to built assets directory when src is not available (distribution)
108 $this->blocks_path = EMBEDPRESS_PATH_BASE . 'assets/blocks/';
109 $this->blocks_url = EMBEDPRESS_URL_ASSETS . 'blocks/';
110 }
111
112
113 // AssetManager is initialized in Core/init.php
114
115 add_action('init', [$this, 'register_blocks']);
116 add_action('admin_enqueue_scripts', [$this, 'enqueue_block_assets']);
117 add_action('admin_enqueue_scripts', [$this, 'enqueue_editor_assets']);
118
119 // Register PDF gallery thumbnail AJAX handlers
120 // Uses Pdf_Thumbnail_Handler (no Elementor dependency) so this works with Gutenberg-only setups
121 add_action('wp_ajax_ep_upload_pdf_thumbnail', ['EmbedPress\Includes\Classes\Pdf_Thumbnail_Handler', 'ajax_upload_pdf_thumbnail']);
122 add_action('wp_ajax_ep_generate_pdf_thumbnail', ['EmbedPress\Includes\Classes\Pdf_Thumbnail_Handler', 'ajax_generate_pdf_thumbnail']);
123 }
124
125 /**
126 * Register all blocks
127 */
128 public function register_blocks()
129 {
130 if (!function_exists('register_block_type')) {
131 return;
132 }
133
134 $elements = (array) get_option(EMBEDPRESS_PLG_NAME . ":elements", []);
135 $g_blocks = isset($elements['gutenberg']) ? (array) $elements['gutenberg'] : [];
136
137 // Self-heal: make sure the DEFAULT-ON blocks are enabled. This runs even
138 // when a `gutenberg` key already exists, so blocks added in a later
139 // release (e.g. google-reviews, pdf-gallery) appear on EXISTING installs
140 // after an update — without it, a site whose saved element list predates
141 // the new block never registers it (the block silently never shows in the
142 // inserter). ensure_default_blocks_enabled() is idempotent (adds only
143 // missing keys), so calling it every load is safe + cheap. We re-read +
144 // only persist when something was actually added (see the method).
145 $missing_default = false;
146 foreach ($this->default_enabled_blocks() as $key) {
147 // array_key_exists (not isset): a block the user explicitly disabled
148 // is stored as '' — that's "present", so we must NOT treat it as
149 // missing and re-enable it. Only a key never seen before triggers the
150 // self-heal.
151 if (!array_key_exists($key, $g_blocks)) { $missing_default = true; break; }
152 }
153 if (empty($elements) || !isset($elements['gutenberg']) || $missing_default) {
154 $this->ensure_default_blocks_enabled();
155 $elements = (array) get_option(EMBEDPRESS_PLG_NAME . ":elements", []);
156 $g_blocks = isset($elements['gutenberg']) ? (array) $elements['gutenberg'] : [];
157 }
158
159 foreach ($this->available_blocks as $block_folder => $block_config) {
160 $setting_key = $block_config['setting_key'];
161
162 // Check if block is enabled in settings
163 if (!empty($g_blocks[$setting_key])) {
164 $this->register_single_block($block_folder, $block_config);
165 } else {
166 // Unregister if disabled
167 if (\WP_Block_Type_Registry::get_instance()->is_registered($block_config['name'])) {
168 unregister_block_type($block_config['name']);
169 }
170 }
171 }
172 }
173
174 /**
175 * Register a single block
176 */
177 private function register_single_block($block_folder, $block_config)
178 {
179 $block_path = $this->blocks_path . $block_folder;
180 $block_json_path = $block_path . '/block.json';
181
182 // Check if we're in development mode (src directory exists) or distribution mode
183 $is_development_mode = file_exists(EMBEDPRESS_PATH_BASE . 'src/Blocks/');
184
185 if ($is_development_mode && file_exists($block_json_path)) {
186 // Development mode: Use block.json files
187 $block_args = [];
188
189 // For blocks that support save function, render_callback is only used for dynamic content
190 if (isset($block_config['render_callback'])) {
191 $block_args['render_callback'] = $block_config['render_callback'];
192 }
193
194 // Add attributes from the old system for compatibility
195 if ($block_config['name'] === 'embedpress/embedpress') {
196 $block_args['attributes'] = $this->get_embedpress_block_attributes();
197 } else if ($block_config['name'] === 'embedpress/embedpress-pdf') {
198 $block_args['attributes'] = $this->get_embedpress_pdf_attributes();
199 } else if ($block_config['name'] === 'embedpress/document') {
200 $block_args['attributes'] = $this->get_embedpress_doc_attributes();
201 } else if ($block_config['name'] === 'embedpress/youtube-block') {
202 $block_args['attributes'] = $this->get_youtube_block_attributes();
203 } else if ($block_config['name'] === 'embedpress/wistia-block') {
204 $block_args['attributes'] = $this->get_wistia_block_attributes();
205 } else if ($block_config['name'] === 'embedpress/pdf-gallery') {
206 $block_args['attributes'] = $this->get_pdf_gallery_attributes();
207 }
208
209 register_block_type($block_json_path, $block_args);
210 } else {
211 // Distribution mode: Register blocks directly without block.json
212 // The JavaScript registration is handled by the bundled blocks.build.js file
213 // We only need to register the server-side render callbacks
214
215 $block_args = [
216 'render_callback' => $block_config['render_callback'] ?? null,
217 ];
218
219 // Attributes for server-side rendering. PREFER the block's shipped
220 // block.json (assets/blocks/<folder>/block.json — included in the
221 // dist zip): it carries the canonical, complete attribute schema, so
222 // any block — including ones with no hardcoded get_*_attributes()
223 // method, e.g. google-reviews — registers its full schema in
224 // distribution mode automatically. Without this, a dynamic block
225 // whose name has no else-if branch below registered with an EMPTY
226 // attribute set, so its saved/SSR attributes were dropped and the
227 // block broke after build (worked in dev because dev mode reads
228 // block.json directly).
229 if (file_exists($block_json_path)) {
230 $block_json = json_decode((string) file_get_contents($block_json_path), true);
231 if (is_array($block_json) && !empty($block_json['attributes']) && is_array($block_json['attributes'])) {
232 $block_args['attributes'] = $block_json['attributes'];
233 }
234 }
235
236 // Fallback (and back-compat) — the hardcoded attribute maps for the
237 // older blocks that predate shipping a block.json. Only used when the
238 // block.json above didn't supply attributes.
239 if (empty($block_args['attributes'])) {
240 if ($block_config['name'] === 'embedpress/embedpress') {
241 $block_args['attributes'] = $this->get_embedpress_block_attributes();
242 } else if ($block_config['name'] === 'embedpress/embedpress-pdf') {
243 $block_args['attributes'] = $this->get_embedpress_pdf_attributes();
244 } else if ($block_config['name'] === 'embedpress/document') {
245 $block_args['attributes'] = $this->get_embedpress_doc_attributes();
246 } else if ($block_config['name'] === 'embedpress/youtube-block') {
247 $block_args['attributes'] = $this->get_youtube_block_attributes();
248 } else if ($block_config['name'] === 'embedpress/wistia-block') {
249 $block_args['attributes'] = $this->get_wistia_block_attributes();
250 } else if ($block_config['name'] === 'embedpress/pdf-gallery') {
251 $block_args['attributes'] = $this->get_pdf_gallery_attributes();
252 }
253 }
254
255 // Only register if not already registered by JavaScript
256 if (!\WP_Block_Type_Registry::get_instance()->is_registered($block_config['name'])) {
257 // Register from the block.json PATH (not just the name) when it
258 // ships in the zip. This lets WP auto-derive the attributes
259 // implied by `supports` — most importantly `align` (and
260 // `anchor`) — which are NOT listed under block.json's
261 // `attributes` key. Passing only a manual `attributes` array
262 // (as before) skipped that derivation, so a ServerSideRender
263 // block (e.g. google-reviews) hit the /wp/v2/block-renderer
264 // endpoint with `attributes[align]` and got a 400
265 // "Invalid parameter(s): attributes" — the production-only bug
266 // that didn't reproduce in dev (dev already registers via the
267 // block.json path). $block_args (render_callback + our explicit
268 // attribute schema) is merged on top of the file metadata.
269 if (file_exists($block_json_path)) {
270 register_block_type($block_json_path, $block_args);
271 } else {
272 register_block_type($block_config['name'], $block_args);
273 }
274 } else {
275 // Block already registered by JavaScript, just update the render callback
276 $block_type = \WP_Block_Type_Registry::get_instance()->get_registered($block_config['name']);
277 if ($block_type && isset($block_config['render_callback'])) {
278 $block_type->render_callback = $block_config['render_callback'];
279 }
280 }
281 }
282 }
283
284 /**
285 * Get EmbedPress block attributes for compatibility
286 */
287 private function get_embedpress_block_attributes()
288 {
289 // Base attributes for all embeds
290 $attributes = [
291 // Core attributes
292 'clientId' => [
293 'type' => 'string',
294 ],
295 'url' => [
296 'type' => 'string',
297 'default' => ''
298 ],
299 'providerName' => [
300 'type' => 'string',
301 'default' => ''
302 ],
303 'embedHTML' => [
304 'type' => 'string',
305 'default' => ''
306 ],
307 'height' => [
308 'type' => 'string',
309 'default' => '600'
310 ],
311 'width' => [
312 'type' => 'string',
313 'default' => '600'
314 ],
315
316 // State attributes
317 'editingURL' => [
318 'type' => 'boolean',
319 'default' => false
320 ],
321 'fetching' => [
322 'type' => 'boolean',
323 'default' => false
324 ],
325 'cannotEmbed' => [
326 'type' => 'boolean',
327 'default' => false
328 ],
329 'interactive' => [
330 'type' => 'boolean',
331 'default' => false
332 ],
333 'align' => [
334 'type' => 'string',
335 'default' => 'center'
336 ],
337
338
339 // Custom Player
340 'customPlayer' => [
341 'type' => 'boolean',
342 'default' => false
343 ],
344 'playerPreset' => [
345 'type' => 'string',
346 'default' => 'preset-default'
347 ],
348 'playerColor' => [
349 'type' => 'string',
350 'default' => '#5b4e96',
351 ],
352 'autoPause' => [
353 'type' => 'boolean',
354 'default' => false
355 ],
356 'posterThumbnail' => [
357 'type' => 'string',
358 'default' => ''
359 ],
360 'playerPip' => [
361 'type' => 'boolean',
362 'default' => true
363 ],
364 'playerRestart' => [
365 'type' => 'boolean',
366 'default' => true
367 ],
368 'playerRewind' => [
369 'type' => 'boolean',
370 'default' => false
371 ],
372 'playerFastForward' => [
373 'type' => 'boolean',
374 'default' => false
375 ],
376 'playerTooltip' => [
377 'type' => 'boolean',
378 'default' => true
379 ],
380 'playerHideControls' => [
381 'type' => 'boolean',
382 'default' => false
383 ],
384 'playerDownload' => [
385 'type' => 'boolean',
386 'default' => true
387 ],
388 // Pro Custom Player features (PRD #81243). These MUST mirror
389 // attributes.js or the render_callback receives stripped values
390 // and the Pro filter (AdvancedPlayerHandlers::build_options)
391 // falls back to schema defaults — e.g. "Show Replay" stays on
392 // even when the editor toggle is off, because the false value
393 // never reaches PHP.
394 'playerAutoResume' => [
395 'type' => 'boolean',
396 'default' => false
397 ],
398 'playerAutoResumeThreshold' => [
399 'type' => 'number',
400 'default' => 30
401 ],
402 'playerEndScreen' => [
403 'type' => 'boolean',
404 'default' => false
405 ],
406 'playerEndScreenMode' => [
407 'type' => 'string',
408 'default' => 'message'
409 ],
410 'playerEndScreenMessage' => [
411 'type' => 'string',
412 'default' => 'Thanks for watching!'
413 ],
414 'playerEndScreenButtonText' => [
415 'type' => 'string',
416 'default' => 'Learn more'
417 ],
418 'playerEndScreenButtonUrl' => [
419 'type' => 'string',
420 'default' => ''
421 ],
422 'playerEndScreenRedirectUrl' => [
423 'type' => 'string',
424 'default' => ''
425 ],
426 'playerEndScreenRedirectNewWindow' => [
427 'type' => 'boolean',
428 'default' => false
429 ],
430 'playerEndScreenCountdown' => [
431 'type' => 'number',
432 'default' => 5
433 ],
434 'playerEndScreenShowReplay' => [
435 'type' => 'boolean',
436 'default' => true
437 ],
438 'playerPrivacyMode' => [
439 'type' => 'boolean',
440 'default' => false
441 ],
442 'playerPrivacyMessage' => [
443 'type' => 'string',
444 'default' => 'Click to load. By playing, you accept third-party cookies.'
445 ],
446 'playerTimedCTA' => [
447 'type' => 'boolean',
448 'default' => false
449 ],
450 'playerTimedCTAItems' => [
451 'type' => 'array',
452 'default' => []
453 ],
454 'playerChapters' => [
455 'type' => 'boolean',
456 'default' => false
457 ],
458 'playerChaptersItems' => [
459 'type' => 'array',
460 'default' => []
461 ],
462 'playerChaptersShowTitle' => [
463 'type' => 'boolean',
464 'default' => true
465 ],
466 'playerEmailCapture' => [
467 'type' => 'boolean',
468 'default' => false
469 ],
470 'playerEmailCaptureTime' => [
471 'type' => 'number',
472 'default' => 30
473 ],
474 'playerEmailCaptureUnit' => [
475 'type' => 'string',
476 'default' => 'seconds'
477 ],
478 'playerEmailCaptureHeadline' => [
479 'type' => 'string',
480 'default' => 'Enter your email to keep watching'
481 ],
482 'playerEmailCaptureRequireName' => [
483 'type' => 'boolean',
484 'default' => false
485 ],
486 'playerEmailCaptureAllowSkip' => [
487 'type' => 'boolean',
488 'default' => false
489 ],
490 'playerEmailCaptureButtonText' => [
491 'type' => 'string',
492 'default' => 'Continue'
493 ],
494 'playerActionLock' => [
495 'type' => 'boolean',
496 'default' => false
497 ],
498 'playerActionLockType' => [
499 'type' => 'string',
500 'default' => 'share'
501 ],
502 'playerActionLockHeadline' => [
503 'type' => 'string',
504 'default' => 'Unlock this video'
505 ],
506 'playerActionLockMessage' => [
507 'type' => 'string',
508 'default' => 'Complete the action below to continue watching.'
509 ],
510 'playerActionLockShareNetworks' => [
511 'type' => 'array',
512 'default' => ['facebook', 'twitter', 'linkedin']
513 ],
514 'playerActionLockShareUrl' => [
515 'type' => 'string',
516 'default' => ''
517 ],
518 'playerActionLockLinkUrl' => [
519 'type' => 'string',
520 'default' => ''
521 ],
522 'playerActionLockLinkText' => [
523 'type' => 'string',
524 'default' => 'Open link'
525 ],
526 'playerActionLockBypassAdmins' => [
527 'type' => 'boolean',
528 'default' => true
529 ],
530 'playerAdaptiveStreaming' => [
531 'type' => 'boolean',
532 'default' => false
533 ],
534 'playerCountryRestriction' => [
535 'type' => 'boolean',
536 'default' => false
537 ],
538 'playerCountryMode' => [
539 'type' => 'string',
540 'default' => 'block'
541 ],
542 'playerCountryList' => [
543 'type' => 'string',
544 'default' => ''
545 ],
546 'playerCountryMessage' => [
547 'type' => 'string',
548 'default' => 'Sorry, this video is not available in your country.'
549 ],
550 'playerLmsTracking' => [
551 'type' => 'boolean',
552 'default' => false
553 ],
554 'playerLmsThreshold' => [
555 'type' => 'number',
556 'default' => 90
557 ],
558 'playerHeatmap' => [
559 'type' => 'boolean',
560 'default' => false
561 ],
562 'playerCdnEnabled' => [
563 'type' => 'boolean',
564 'default' => true
565 ],
566 'powered_by' => [
567 'type' => 'boolean',
568 'default' => true
569 ],
570 ];
571
572 // Add provider-specific attributes
573 $attributes = array_merge($attributes, $this->get_youtube_attributes());
574 $attributes = array_merge($attributes, $this->get_vimeo_attributes());
575 $attributes = array_merge($attributes, $this->get_wistia_attributes());
576 $attributes = array_merge($attributes, $this->get_instagram_attributes());
577 $attributes = array_merge($attributes, $this->get_calendly_attributes());
578 $attributes = array_merge($attributes, $this->get_podcast_attributes());
579 $attributes = array_merge($attributes, $this->get_google_photos_attributes());
580 $attributes = array_merge($attributes, $this->get_nft_attributes());
581 $attributes = array_merge($attributes, $this->get_ad_manager_attributes());
582 $attributes = array_merge($attributes, $this->get_content_protection_attributes());
583 $attributes = array_merge($attributes, $this->get_social_sharing_attributes());
584 $attributes = array_merge($attributes, $this->get_custom_branding_attributes());
585
586 return $attributes;
587 }
588
589 /**
590 * Get PDF-specific attributes
591 */
592 private function get_embedpress_pdf_attributes()
593 {
594 $attributes = [
595 'clientId' => [
596 'type' => 'string',
597 ],
598 'id' => [
599 'type' => 'string'
600 ],
601 'href' => [
602 'type' => 'string'
603 ],
604 'fileName' => [
605 'type' => 'string',
606 ],
607 'mime' => [
608 'type' => 'string',
609 ],
610 'url' => [
611 'type' => 'string',
612 'default' => ''
613 ],
614 'height' => [
615 'type' => 'string',
616 'default' => '600'
617 ],
618 'width' => [
619 'type' => 'string',
620 'default' => '600'
621 ],
622 'pageNumber' => [
623 'type' => 'number',
624 'default' => 1
625 ],
626 'viewerStyle' => [
627 'type' => 'string',
628 'default' => 'modern'
629 ],
630 'displayMode' => [
631 'type' => 'string',
632 'default' => 'inline'
633 ],
634 'lightboxThumbnail' => [
635 'type' => 'string',
636 'default' => ''
637 ],
638 'lightboxAlign' => [
639 'type' => 'string',
640 'default' => 'left'
641 ],
642 'triggerText' => [
643 'type' => 'string',
644 'default' => 'View PDF'
645 ],
646 'triggerColor' => [
647 'type' => 'string',
648 'default' => ''
649 ],
650 'triggerBgColor' => [
651 'type' => 'string',
652 'default' => ''
653 ],
654 'triggerFontSize' => [
655 'type' => 'string',
656 'default' => ''
657 ],
658 'triggerBorderRadius' => [
659 'type' => 'string',
660 'default' => ''
661 ],
662 'themeMode' => [
663 'type' => 'string',
664 'default' => 'default'
665 ],
666 'customColor' => [
667 'type' => 'string',
668 'default' => '#403A81'
669 ],
670 'toolbar' => [
671 'type' => 'boolean',
672 'default' => true
673 ],
674 'presentation' => [
675 'type' => 'boolean',
676 'default' => true
677 ],
678 'lazyLoad' => [
679 'type' => 'boolean',
680 'default' => false
681 ],
682 'position' => [
683 'type' => 'string',
684 'default' => 'top'
685 ],
686 'flipbook_toolbar_position' => [
687 'type' => 'string',
688 'default' => 'bottom'
689 ],
690 'download' => [
691 'type' => 'boolean',
692 'default' => true
693 ],
694 'open' => [
695 'type' => 'boolean',
696 'default' => false
697 ],
698 'copy_text' => [
699 'type' => 'boolean',
700 'default' => true
701 ],
702 'add_text' => [
703 'type' => 'boolean',
704 'default' => true
705 ],
706 'draw' => [
707 'type' => 'boolean',
708 'default' => true
709 ],
710 'add_image' => [
711 'type' => 'boolean',
712 'default' => true
713 ],
714 'zoomIn' => [
715 'type' => 'boolean',
716 'default' => true
717 ],
718 'zoomOut' => [
719 'type' => 'boolean',
720 'default' => true
721 ],
722 'fitView' => [
723 'type' => 'boolean',
724 'default' => true
725 ],
726 'bookmark' => [
727 'type' => 'boolean',
728 'default' => true
729 ],
730 'doc_details' => [
731 'type' => 'boolean',
732 'default' => true
733 ],
734 'doc_rotation' => [
735 'type' => 'boolean',
736 'default' => true
737 ],
738 'unitoption' => [
739 'type' => 'string',
740 'default' => '%'
741 ],
742 'powered_by' => [
743 'type' => 'boolean',
744 'default' => true
745 ],
746 'watermarkText' => [
747 'type' => 'string',
748 'default' => ''
749 ],
750 'watermarkFontSize' => [
751 'type' => 'number',
752 'default' => 48
753 ],
754 'watermarkColor' => [
755 'type' => 'string',
756 'default' => '#000000'
757 ],
758 'watermarkOpacity' => [
759 'type' => 'number',
760 'default' => 15
761 ],
762 'watermarkStyle' => [
763 'type' => 'string',
764 'default' => 'center'
765 ],
766
767 ];
768
769 $attributes = array_merge($attributes, $this->get_content_protection_attributes());
770 $attributes = array_merge($attributes, $this->get_social_sharing_attributes());
771 $attributes = array_merge($attributes, $this->get_custom_branding_attributes());
772
773 return $attributes;
774 }
775
776 /**
777 * Get PDF Gallery block attributes
778 */
779 private function get_pdf_gallery_attributes()
780 {
781 return [
782 'clientId' => ['type' => 'string'],
783 'pdfItems' => ['type' => 'array', 'default' => []],
784 'layout' => ['type' => 'string', 'default' => 'grid'],
785 'columns' => ['type' => 'number', 'default' => 3],
786 'columnsTablet' => ['type' => 'number', 'default' => 2],
787 'columnsMobile' => ['type' => 'number', 'default' => 1],
788 'gap' => ['type' => 'number', 'default' => 20],
789 'thumbnailAspectRatio' => ['type' => 'string', 'default' => '4:3'],
790 'thumbnailBorderRadius' => ['type' => 'number', 'default' => 8],
791 'carouselAutoplay' => ['type' => 'boolean', 'default' => false],
792 'carouselAutoplaySpeed' => ['type' => 'number', 'default' => 3000],
793 'carouselLoop' => ['type' => 'boolean', 'default' => true],
794 'carouselArrows' => ['type' => 'boolean', 'default' => true],
795 'carouselDots' => ['type' => 'boolean', 'default' => false],
796 'slidesPerView' => ['type' => 'number', 'default' => 3],
797 'viewerStyle' => ['type' => 'string', 'default' => 'modern'],
798 'themeMode' => ['type' => 'string', 'default' => 'default'],
799 'customColor' => ['type' => 'string', 'default' => '#403A81'],
800 'toolbar' => ['type' => 'boolean', 'default' => true],
801 'position' => ['type' => 'string', 'default' => 'top'],
802 'flipbook_toolbar_position' => ['type' => 'string', 'default' => 'bottom'],
803 'presentation' => ['type' => 'boolean', 'default' => true],
804 'download' => ['type' => 'boolean', 'default' => true],
805 'copy_text' => ['type' => 'boolean', 'default' => true],
806 'draw' => ['type' => 'boolean', 'default' => true],
807 'add_text' => ['type' => 'boolean', 'default' => true],
808 'add_image' => ['type' => 'boolean', 'default' => true],
809 'doc_rotation' => ['type' => 'boolean', 'default' => true],
810 'doc_details' => ['type' => 'boolean', 'default' => true],
811 'zoomIn' => ['type' => 'boolean', 'default' => true],
812 'zoomOut' => ['type' => 'boolean', 'default' => true],
813 'fitView' => ['type' => 'boolean', 'default' => true],
814 'bookmark' => ['type' => 'boolean', 'default' => true],
815 'powered_by' => ['type' => 'boolean', 'default' => true],
816 'watermarkText' => ['type' => 'string', 'default' => ''],
817 'watermarkFontSize' => ['type' => 'number', 'default' => 48],
818 'watermarkColor' => ['type' => 'string', 'default' => '#000000'],
819 'watermarkOpacity' => ['type' => 'number', 'default' => 15],
820 'watermarkStyle' => ['type' => 'string', 'default' => 'center'],
821 ];
822 }
823
824 private function get_embedpress_doc_attributes()
825 {
826 $attributes = [
827 'clientId' => [
828 'type' => 'string',
829 ],
830
831 'id' => [
832 'type' => 'string',
833 ],
834 'href' => [
835 'type' => 'string',
836 ],
837 'height' => [
838 'type' => 'string',
839 'default' => '600'
840 ],
841 'width' => [
842 'type' => 'string',
843 'default' => '600'
844 ],
845 'fileName' => [
846 'type' => 'string',
847 ],
848 'mime' => [
849 'type' => 'string',
850 ],
851 'powered_by' => [
852 'type' => 'boolean',
853 'default' => true,
854 ],
855 'presentation' => [
856 'type' => 'boolean',
857 'default' => true,
858 ],
859 'docViewer' => [
860 'type' => 'string',
861 'default' => 'custom',
862 ],
863 'themeMode' => [
864 'type' => 'string',
865 'default' => 'default',
866 ],
867 'customColor' => [
868 'type' => 'string',
869 'default' => '#403A81',
870 ],
871 'position' => [
872 'type' => 'string',
873 'default' => 'top',
874 ],
875 'download' => [
876 'type' => 'boolean',
877 'default' => true,
878 ],
879 'open' => [
880 'type' => 'boolean',
881 'default' => false,
882 ],
883 'copy_text' => [
884 'type' => 'boolean',
885 'default' => true,
886 ],
887 'draw' => [
888 'type' => 'boolean',
889 'default' => true,
890 ],
891 'toolbar' => [
892 'type' => 'boolean',
893 'default' => true,
894 ],
895 'doc_rotation' => [
896 'type' => 'boolean',
897 'default' => true,
898 ],
899 ];
900
901 $attributes = array_merge($attributes, $this->get_content_protection_attributes());
902 $attributes = array_merge($attributes, $this->get_social_sharing_attributes());
903 $attributes = array_merge($attributes, $this->get_custom_branding_attributes());
904
905 return $attributes;
906 }
907
908 /**
909 * Get YouTube block-specific attributes (for legacy youtube-block)
910 */
911 private function get_youtube_block_attributes()
912 {
913 return [
914 'url' => [
915 'type' => 'string',
916 'default' => ''
917 ],
918 'iframeSrc' => [
919 'type' => 'string',
920 'default' => ''
921 ],
922 'mediaId' => [
923 'type' => 'string',
924 'default' => ''
925 ],
926 'align' => [
927 'type' => 'string',
928 'default' => 'center'
929 ]
930 ];
931 }
932
933 /**
934 * Get Wistia block-specific attributes (for legacy wistia-block)
935 */
936 private function get_wistia_block_attributes()
937 {
938 return [
939 'url' => [
940 'type' => 'string',
941 'default' => ''
942 ],
943 'iframeSrc' => [
944 'type' => 'string',
945 'default' => ''
946 ],
947 'align' => [
948 'type' => 'string',
949 'default' => 'center'
950 ]
951 ];
952 }
953
954 /**
955 * Get ContenProtection-specific attributes
956 */
957 private function get_content_protection_attributes()
958 {
959 return [
960 'lockContent' => [
961 'type' => 'boolean',
962 'default' => false
963 ],
964 'protectionType' => [
965 'type' => 'string',
966 'default' => 'password'
967 ],
968 'userRole' => [
969 'type' => 'array',
970 'default' => []
971 ],
972 'protectionMessage' => [
973 'type' => 'string',
974 'default' => 'You do not have access to this content. Only users with the following roles can view it: [user_roles]'
975 ],
976 'contentPassword' => [
977 'type' => 'string',
978 'default' => ''
979 ],
980 'lockHeading' => [
981 'type' => 'string',
982 'default' => 'Content Locked'
983 ],
984 'lockSubHeading' => [
985 'type' => 'string',
986 'default' => 'Content is locked and requires password to access it.'
987 ],
988 'lockErrorMessage' => [
989 'type' => 'string',
990 'default' => 'Oops, that wasn\'t the right password. Try again.'
991 ],
992 'passwordPlaceholder' => [
993 'type' => 'string',
994 'default' => 'Password'
995 ],
996 'submitButtonText' => [
997 'type' => 'string',
998 'default' => 'Unlock'
999 ],
1000 'submitUnlockingText' => [
1001 'type' => 'string',
1002 'default' => 'Unlocking'
1003 ],
1004 'enableFooterMessage' => [
1005 'type' => 'boolean',
1006 'default' => false
1007 ],
1008 'footerMessage' => [
1009 'type' => 'string',
1010 'default' => 'In case you don\'t have the password, kindly reach out to content owner or administrator to request access.'
1011 ],
1012 ];
1013 }
1014
1015 /**
1016 * Get SocialSharing-specific attributes
1017 */
1018 private function get_social_sharing_attributes()
1019 {
1020 return [
1021 'contentShare' => [
1022 'type' => 'boolean',
1023 'default' => false
1024 ],
1025 'sharePosition' => [
1026 'type' => 'string',
1027 'default' => 'right'
1028 ],
1029 'customTitle' => [
1030 'type' => 'string',
1031 'default' => ''
1032 ],
1033 'customDescription' => [
1034 'type' => 'string',
1035 'default' => ''
1036 ],
1037 'customThumbnail' => [
1038 'type' => 'string',
1039 'default' => ''
1040 ],
1041 'shareFacebook' => [
1042 'type' => 'boolean',
1043 'default' => true
1044 ],
1045 'shareTwitter' => [
1046 'type' => 'boolean',
1047 'default' => true
1048 ],
1049 'sharePinterest' => [
1050 'type' => 'boolean',
1051 'default' => true
1052 ],
1053 'shareLinkedin' => [
1054 'type' => 'boolean',
1055 'default' => true
1056 ],
1057 ];
1058 }
1059
1060 /**
1061 * Get CustomBranding-specific attributes
1062 */
1063 private function get_custom_branding_attributes()
1064 {
1065 return [
1066 'customlogo' => [
1067 'type' => 'string',
1068 'default' => ''
1069 ],
1070 'logoX' => [
1071 'type' => 'number',
1072 'default' => 5
1073 ],
1074 'logoY' => [
1075 'type' => 'number',
1076 'default' => 10
1077 ],
1078 'customlogoUrl' => [
1079 'type' => 'string',
1080 ],
1081 'logoOpacity' => [
1082 'type' => 'number',
1083 'default' => 0.6
1084 ],
1085 ];
1086 }
1087
1088
1089 /**
1090 * Get YouTube-specific attributes
1091 */
1092 private function get_youtube_attributes()
1093 {
1094 return [
1095 // YouTube specific attributes
1096 'ispagination' => [
1097 'type' => 'boolean',
1098 'default' => true
1099 ],
1100 'ytChannelLayout' => [
1101 // CHANNEL URLs only. Empty default lets the provider use the
1102 // gallery default. Inspector's explicit pick still overrides.
1103 'type' => 'string',
1104 'default' => ''
1105 ],
1106 'ytPlaylistLayout' => [
1107 // PLAYLIST URLs only — separate dropdown so channel pick can't
1108 // leak in. Values: '' (default: queue) | 'queue' | 'theatre'.
1109 'type' => 'string',
1110 'default' => ''
1111 ],
1112 'ytPlaylistMode' => [
1113 // 'playlist' renders the queue/gallery/list/grid/carousel UI for
1114 // any URL with list=…. 'single' renders just the v= video.
1115 'type' => 'string',
1116 'default' => 'playlist'
1117 ],
1118 'pagesize' => [
1119 'type' => 'string',
1120 'default' => '6'
1121 ],
1122 'columns' => [
1123 'type' => 'string',
1124 'default' => '3'
1125 ],
1126 'gapbetweenvideos' => [
1127 'type' => 'number',
1128 'default' => 30
1129 ],
1130 'videosize' => [
1131 'type' => 'string',
1132 'default' => 'fixed',
1133 ],
1134 'starttime' => [
1135 'type' => 'string',
1136 ],
1137 'endtime' => [
1138 'type' => 'string',
1139 ],
1140 'autoplay' => [
1141 'type' => 'boolean',
1142 'default' => false,
1143 ],
1144 'muteVideo' => [
1145 'type' => 'boolean',
1146 'default' => true,
1147 ],
1148 'controls' => [
1149 'type' => 'string',
1150 ],
1151 'fullscreen' => [
1152 'type' => 'boolean',
1153 'default' => true,
1154 ],
1155 'videoannotations' => [
1156 'type' => 'boolean',
1157 'default' => true,
1158 ],
1159 'progressbarcolor' => [
1160 'type' => 'string',
1161 'default' => 'red'
1162 ],
1163 'closedcaptions' => [
1164 'type' => 'boolean',
1165 'default' => true,
1166 ],
1167 'modestbranding' => [
1168 'type' => 'string',
1169 ],
1170 'relatedvideos' => [
1171 'type' => 'boolean',
1172 'default' => true,
1173 ],
1174 'showinfo' => [
1175 'type' => 'boolean',
1176 'default' => true,
1177 ],
1178 'showLoop' => [
1179 'type' => 'boolean',
1180 'default' => true,
1181 ],
1182 ];
1183 }
1184
1185 /**
1186 * Get Vimeo-specific attributes
1187 */
1188 private function get_vimeo_attributes()
1189 {
1190 return [
1191 'vimeoAutoplay' => [
1192 'type' => 'boolean',
1193 'default' => false,
1194 ],
1195 'vimeoLoop' => [
1196 'type' => 'boolean',
1197 'default' => false,
1198 ],
1199 'vimeoPortrait' => [
1200 'type' => 'boolean',
1201 'default' => true,
1202 ],
1203 'vimeoTitle' => [
1204 'type' => 'boolean',
1205 'default' => true,
1206 ],
1207 'vimeoByline' => [
1208 'type' => 'boolean',
1209 'default' => true,
1210 ],
1211 'vimeoColor' => [
1212 'type' => 'string',
1213 'default' => '#00adef'
1214 ],
1215 'vimeoDnt' => [
1216 'type' => 'boolean',
1217 'default' => false,
1218 ],
1219 // Additional Vimeo attributes
1220 'vstarttime' => [
1221 'type' => 'string',
1222 ],
1223 'vautoplay' => [
1224 'type' => 'boolean',
1225 'default' => false
1226 ],
1227 'vscheme' => [
1228 'type' => 'string',
1229 ],
1230 'vtitle' => [
1231 'type' => 'boolean',
1232 'default' => true
1233 ],
1234 'vauthor' => [
1235 'type' => 'boolean',
1236 'default' => true
1237 ],
1238 'vavatar' => [
1239 'type' => 'boolean',
1240 'default' => true
1241 ],
1242 'vloop' => [
1243 'type' => 'boolean',
1244 'default' => false
1245 ],
1246 'vautopause' => [
1247 'type' => 'boolean',
1248 'default' => false
1249 ],
1250 'vdnt' => [
1251 'type' => 'boolean',
1252 'default' => false
1253 ],
1254 ];
1255 }
1256
1257 /**
1258 * Get Wistia-specific attributes
1259 */
1260 private function get_wistia_attributes()
1261 {
1262 return [
1263 'wstarttime' => [
1264 'type' => 'string',
1265 ],
1266 'wautoplay' => [
1267 'type' => 'boolean',
1268 'default' => true
1269 ],
1270 'scheme' => [
1271 'type' => 'string',
1272 ],
1273 'captions' => [
1274 'type' => 'boolean',
1275 'default' => true
1276 ],
1277 'playbutton' => [
1278 'type' => 'boolean',
1279 'default' => true
1280 ],
1281 'smallplaybutton' => [
1282 'type' => 'boolean',
1283 'default' => true
1284 ],
1285 'playbar' => [
1286 'type' => 'boolean',
1287 'default' => true
1288 ],
1289 'resumable' => [
1290 'type' => 'boolean',
1291 'default' => true
1292 ],
1293 'wistiafocus' => [
1294 'type' => 'boolean',
1295 'default' => true
1296 ],
1297 'volumecontrol' => [
1298 'type' => 'boolean',
1299 'default' => true
1300 ],
1301 'volume' => [
1302 'type' => 'number',
1303 'default' => 100
1304 ],
1305 'rewind' => [
1306 'type' => 'boolean',
1307 'default' => false
1308 ],
1309 'wfullscreen' => [
1310 'type' => 'boolean',
1311 'default' => true
1312 ],
1313 ];
1314 }
1315
1316 /**
1317 * Get Instagram-specific attributes
1318 */
1319 private function get_instagram_attributes()
1320 {
1321 return [
1322 'instaLayout' => [
1323 'type' => 'string',
1324 'default' => 'insta-grid'
1325 ],
1326 'carouselArrows' => [
1327 'type' => 'boolean',
1328 'default' => true
1329 ],
1330 'carouselSpacing' => [
1331 'type' => 'string',
1332 'default' => '0'
1333 ],
1334 'carouselDots' => [
1335 'type' => 'boolean',
1336 'default' => false
1337 ],
1338 // Instagram Feed attributes
1339 'instafeedFeedType' => [
1340 'type' => 'string',
1341 'default' => 'user_account_type',
1342 ],
1343 'instafeedAccountType' => [
1344 'type' => 'string',
1345 'default' => 'personal',
1346 ],
1347 'instafeedProfileImage' => [
1348 'type' => 'boolean',
1349 'default' => true,
1350 ],
1351 'instafeedProfileImageUrl' => [
1352 'type' => 'string',
1353 'default' => '',
1354 ],
1355 'instafeedFollowBtn' => [
1356 'type' => 'boolean',
1357 'default' => true,
1358 ],
1359 'instafeedFollowBtnLabel' => [
1360 'type' => 'string',
1361 'default' => 'Follow',
1362 ],
1363 'instafeedPostsCount' => [
1364 'type' => 'boolean',
1365 'default' => true,
1366 ],
1367 'instafeedPostsCountText' => [
1368 'type' => 'string',
1369 'default' => '[count] posts',
1370 ],
1371 'instafeedFollowersCount' => [
1372 'type' => 'boolean',
1373 'default' => true,
1374 ],
1375 'instafeedFollowersCountText' => [
1376 'type' => 'string',
1377 'default' => '[count] followers',
1378 ],
1379 'instafeedAccName' => [
1380 'type' => 'boolean',
1381 'default' => true,
1382 ],
1383 'instafeedColumns' => [
1384 'type' => 'string',
1385 'default' => '3'
1386 ],
1387 'instafeedColumnsGap' => [
1388 'type' => 'string',
1389 'default' => '5'
1390 ],
1391 'instafeedPostsPerPage' => [
1392 'type' => 'string',
1393 'default' => '12'
1394 ],
1395 'instafeedTab' => [
1396 'type' => 'boolean',
1397 'default' => true,
1398 ],
1399 'instafeedLikesCount' => [
1400 'type' => 'boolean',
1401 'default' => true,
1402 ],
1403 'instafeedCommentsCount' => [
1404 'type' => 'boolean',
1405 'default' => true,
1406 ],
1407 'instafeedPopup' => [
1408 'type' => 'boolean',
1409 'default' => true,
1410 ],
1411 'instafeedPopupFollowBtn' => [
1412 'type' => 'boolean',
1413 'default' => true,
1414 ],
1415 'instafeedPopupFollowBtnLabel' => [
1416 'type' => 'string',
1417 'default' => 'Follow',
1418 ],
1419 'instafeedLoadmore' => [
1420 'type' => 'boolean',
1421 'default' => true,
1422 ],
1423 'instafeedLoadmoreLabel' => [
1424 'type' => 'string',
1425 'default' => 'Load More',
1426 ],
1427 'slidesShow' => [
1428 'type' => 'string',
1429 'default' => '4'
1430 ],
1431 'slidesScroll' => [
1432 'type' => 'string',
1433 'default' => '4'
1434 ],
1435 'carouselAutoplay' => [
1436 'type' => 'boolean',
1437 'default' => false
1438 ],
1439 'autoplaySpeed' => [
1440 'type' => 'string',
1441 'default' => '3000'
1442 ],
1443 'transitionSpeed' => [
1444 'type' => 'string',
1445 'default' => '1000'
1446 ],
1447 'carouselLoop' => [
1448 'type' => 'boolean',
1449 'default' => true
1450 ],
1451 ];
1452 }
1453
1454 /**
1455 * Get Calendly-specific attributes
1456 */
1457 private function get_calendly_attributes()
1458 {
1459 return [
1460 'cEmbedType' => [
1461 'type' => 'string',
1462 'default' => 'inline'
1463 ],
1464 'calendlyData' => [
1465 'type' => 'boolean',
1466 'default' => false
1467 ],
1468 'hideCookieBanner' => [
1469 'type' => 'boolean',
1470 'default' => false
1471 ],
1472 'hideEventTypeDetails' => [
1473 'type' => 'boolean',
1474 'default' => false
1475 ],
1476 'cBackgroundColor' => [
1477 'type' => 'string',
1478 'default' => 'ffffff'
1479 ],
1480 'cTextColor' => [
1481 'type' => 'string',
1482 'default' => '1A1A1A'
1483 ],
1484 'cButtonLinkColor' => [
1485 'type' => 'string',
1486 'default' => '0000FF'
1487 ],
1488 'cPopupButtonText' => [
1489 'type' => 'string',
1490 'default' => 'Schedule time with me'
1491 ],
1492 'cPopupButtonBGColor' => [
1493 'type' => 'string',
1494 'default' => '0000FF'
1495 ],
1496 'cPopupButtonTextColor' => [
1497 'type' => 'string',
1498 'default' => 'FFFFFF'
1499 ],
1500 'cPopupLinkText' => [
1501 'type' => 'string',
1502 'default' => 'Schedule time with me'
1503 ],
1504 ];
1505 }
1506
1507 /**
1508 * Get Podcast-specific attributes (Spreaker)
1509 */
1510 private function get_podcast_attributes()
1511 {
1512 return [
1513 // Spreaker attributes
1514 'theme' => [
1515 'type' => 'string',
1516 'default' => 'light'
1517 ],
1518 'color' => [
1519 'type' => 'string',
1520 'default' => ''
1521 ],
1522 'coverImageUrl' => [
1523 'type' => 'string',
1524 'default' => ''
1525 ],
1526 'playlist' => [
1527 'type' => 'boolean',
1528 'default' => false
1529 ],
1530 'playlistContinuous' => [
1531 'type' => 'boolean',
1532 'default' => false
1533 ],
1534 'playlistLoop' => [
1535 'type' => 'boolean',
1536 'default' => false
1537 ],
1538 'playlistAutoupdate' => [
1539 'type' => 'boolean',
1540 'default' => true
1541 ],
1542 'chaptersImage' => [
1543 'type' => 'boolean',
1544 'default' => true
1545 ],
1546 'episodeImagePosition' => [
1547 'type' => 'string',
1548 'default' => 'right'
1549 ],
1550 'hideLikes' => [
1551 'type' => 'boolean',
1552 'default' => false
1553 ],
1554 'hideComments' => [
1555 'type' => 'boolean',
1556 'default' => false
1557 ],
1558 'hideSharing' => [
1559 'type' => 'boolean',
1560 'default' => false
1561 ],
1562 'hideLogo' => [
1563 'type' => 'boolean',
1564 'default' => false
1565 ],
1566 'hideEpisodeDescription' => [
1567 'type' => 'boolean',
1568 'default' => false
1569 ],
1570 'hidePlaylistDescriptions' => [
1571 'type' => 'boolean',
1572 'default' => false
1573 ],
1574 'hidePlaylistImages' => [
1575 'type' => 'boolean',
1576 'default' => false
1577 ],
1578 'hideDownload' => [
1579 'type' => 'boolean',
1580 'default' => false
1581 ],
1582 ];
1583 }
1584
1585 /**
1586 * Get Google Photos-specific attributes
1587 */
1588 private function get_google_photos_attributes()
1589 {
1590 return [
1591 'mode' => [
1592 'type' => 'string',
1593 'default' => 'carousel'
1594 ],
1595 'imageWidth' => [
1596 'type' => 'number',
1597 'default' => 800
1598 ],
1599 'imageHeight' => [
1600 'type' => 'number',
1601 'default' => 600
1602 ],
1603 'showTitle' => [
1604 'type' => 'boolean',
1605 'default' => true
1606 ],
1607 'playerAutoplay' => [
1608 'type' => 'boolean',
1609 'default' => false
1610 ],
1611 'delay' => [
1612 'type' => 'number',
1613 'default' => 5
1614 ],
1615 'repeat' => [
1616 'type' => 'boolean',
1617 'default' => true
1618 ],
1619 'mediaitemsAspectRatio' => [
1620 'type' => 'boolean',
1621 'default' => true
1622 ],
1623 'mediaitemsEnlarge' => [
1624 'type' => 'boolean',
1625 'default' => false
1626 ],
1627 'mediaitemsStretch' => [
1628 'type' => 'boolean',
1629 'default' => false
1630 ],
1631 'mediaitemsCover' => [
1632 'type' => 'boolean',
1633 'default' => false
1634 ],
1635 'backgroundColor' => [
1636 'type' => 'string',
1637 'default' => '#000000'
1638 ],
1639 'expiration' => [
1640 'type' => 'number',
1641 'default' => 60
1642 ],
1643 ];
1644 }
1645
1646 /**
1647 * Get NFT/OpenSea-specific attributes
1648 */
1649 private function get_nft_attributes()
1650 {
1651 return [
1652 'limit' => [
1653 'type' => 'number',
1654 'default' => 20
1655 ],
1656 'itemperpage' => [
1657 'type' => 'number',
1658 'default' => 9
1659 ],
1660 'loadmore' => [
1661 'type' => 'boolean',
1662 'default' => false
1663 ],
1664 'loadmorelabel' => [
1665 'type' => 'text',
1666 'default' => 'Load More'
1667 ],
1668 'orderby' => [
1669 'type' => 'string',
1670 'default' => 'desc'
1671 ],
1672 'gapbetweenitem' => [
1673 'type' => 'number',
1674 'default' => 30
1675 ],
1676 'layout' => [
1677 'type' => 'string',
1678 'default' => 'ep-grid'
1679 ],
1680 'preset' => [
1681 'type' => 'string',
1682 'default' => 'preset-default'
1683 ],
1684 'nftperrow' => [
1685 'type' => 'number',
1686 'default' => 3
1687 ],
1688 'collectionname' => [
1689 'type' => 'boolean',
1690 'default' => true
1691 ],
1692 'nftimage' => [
1693 'type' => 'boolean',
1694 'default' => true
1695 ],
1696 'nfttitle' => [
1697 'type' => 'boolean',
1698 'default' => true
1699 ],
1700 'nftcreator' => [
1701 'type' => 'boolean',
1702 'default' => true
1703 ],
1704 'prefix_nftcreator' => [
1705 'type' => 'string',
1706 'default' => 'Created By'
1707 ],
1708 'nftprice' => [
1709 'type' => 'boolean',
1710 'default' => true
1711 ],
1712 'prefix_nftprice' => [
1713 'type' => 'string',
1714 'default' => 'Current Price'
1715 ],
1716 'nftlastsale' => [
1717 'type' => 'boolean',
1718 'default' => true
1719 ],
1720 'prefix_nftlastsale' => [
1721 'type' => 'string',
1722 'default' => 'Last Sale'
1723 ],
1724 'nftbutton' => [
1725 'type' => 'boolean',
1726 'default' => true
1727 ],
1728 'nftrank' => [
1729 'type' => 'boolean',
1730 'default' => true
1731 ],
1732 'label_nftrank' => [
1733 'type' => 'string',
1734 'default' => 'Rank'
1735 ],
1736 'nftdetails' => [
1737 'type' => 'boolean',
1738 'default' => true
1739 ],
1740 'label_nftdetails' => [
1741 'type' => 'string',
1742 'default' => 'Details'
1743 ],
1744 'label_nftbutton' => [
1745 'type' => 'string',
1746 'default' => 'See Details'
1747 ],
1748 'alignment' => [
1749 'type' => 'string',
1750 'default' => 'ep-item-center'
1751 ],
1752 // Color and Typography for NFT
1753 'itemBGColor' => [
1754 'type' => 'string',
1755 ],
1756 'collectionNameColor' => [
1757 'type' => 'string',
1758 ],
1759 'collectionNameFZ' => [
1760 'type' => 'number',
1761 ],
1762 'titleColor' => [
1763 'type' => 'string',
1764 ],
1765 'titleFontsize' => [
1766 'type' => 'number',
1767 ],
1768 'creatorColor' => [
1769 'type' => 'string',
1770 ],
1771 'creatorFontsize' => [
1772 'type' => 'number',
1773 ],
1774 'creatorLinkColor' => [
1775 'type' => 'string',
1776 ],
1777 'creatorLinkFontsize' => [
1778 'type' => 'number',
1779 ],
1780 'priceLabelColor' => [
1781 'type' => 'string',
1782 ],
1783 'priceLabelFontsize' => [
1784 'type' => 'number',
1785 ],
1786 'priceColor' => [
1787 'type' => 'string',
1788 ],
1789 'priceFontsize' => [
1790 'type' => 'number',
1791 ],
1792 'priceUSDColor' => [
1793 'type' => 'string',
1794 ],
1795 'priceUSDFontsize' => [
1796 'type' => 'number',
1797 ],
1798 'lastSaleLabelColor' => [
1799 'type' => 'string',
1800 ],
1801 'lastSaleLabelFontsize' => [
1802 'type' => 'number',
1803 ],
1804 'lastSaleColor' => [
1805 'type' => 'string',
1806 ],
1807 'lastSaleFontsize' => [
1808 'type' => 'number',
1809 ],
1810 'lastSaleUSDColor' => [
1811 'type' => 'string',
1812 ],
1813 'lastSaleUSDFontsize' => [
1814 'type' => 'number',
1815 ],
1816 'buttonTextColor' => [
1817 'type' => 'string',
1818 ],
1819 'buttonBackgroundColor' => [
1820 'type' => 'string',
1821 ],
1822 'buttonTextFontsize' => [
1823 'type' => 'number',
1824 ],
1825 'loadmoreTextColor' => [
1826 'type' => 'string',
1827 ],
1828 'loadmoreBackgroundColor' => [
1829 'type' => 'string',
1830 ],
1831 'loadmoreTextFontsize' => [
1832 'type' => 'number',
1833 ],
1834 'rankBtnColor' => [
1835 'type' => 'string',
1836 ],
1837 'rankBtnBorderColor' => [
1838 'type' => 'string',
1839 ],
1840 'rankBtnFZ' => [
1841 'type' => 'number',
1842 ],
1843 'rankLabelColor' => [
1844 'type' => 'string',
1845 ],
1846 'rankLabelFZ' => [
1847 'type' => 'number',
1848 ],
1849 'detialTitleColor' => [
1850 'type' => 'string',
1851 ],
1852 'detialTitleFZ' => [
1853 'type' => 'number',
1854 ],
1855 'detailTextColor' => [
1856 'type' => 'string',
1857 ],
1858 'detailTextLinkColor' => [
1859 'type' => 'string',
1860 ],
1861 'detailTextFZ' => [
1862 'type' => 'number',
1863 ],
1864 ];
1865 }
1866
1867 /**
1868 * Get Ad Manager-specific attributes
1869 */
1870 private function get_ad_manager_attributes()
1871 {
1872 return [
1873 'adManager' => [
1874 'type' => 'boolean',
1875 'default' => false
1876 ],
1877 'adSource' => [
1878 'type' => 'string',
1879 'default' => 'video'
1880 ],
1881 'adContent' => [
1882 'type' => 'object',
1883 ],
1884 'adFileUrl' => [
1885 'type' => 'string',
1886 'default' => ''
1887 ],
1888 'adWidth' => [
1889 'type' => 'string',
1890 'default' => '300'
1891 ],
1892 'adHeight' => [
1893 'type' => 'string',
1894 'default' => '200'
1895 ],
1896 'adXPosition' => [
1897 'type' => 'number',
1898 'default' => 25
1899 ],
1900 'adYPosition' => [
1901 'type' => 'number',
1902 'default' => 10
1903 ],
1904 'adUrl' => [
1905 'type' => 'string',
1906 'default' => ''
1907 ],
1908 'adStart' => [
1909 'type' => 'string',
1910 'default' => '10'
1911 ],
1912 'adSkipButton' => [
1913 'type' => 'boolean',
1914 'default' => true
1915 ],
1916 'adSkipButtonAfter' => [
1917 'type' => 'string',
1918 'default' => '5'
1919 ],
1920 ];
1921 }
1922
1923 /**
1924 * Enqueue block assets for both frontend and backend
1925 * Now handled by AssetManager
1926 */
1927 public function enqueue_block_assets()
1928 {
1929 // Assets are now handled by the centralized AssetManager
1930 // This method is kept for backward compatibility
1931 }
1932
1933 /**
1934 * Enqueue editor assets
1935 */
1936 public function enqueue_editor_assets()
1937 {
1938 // Assets are now handled by AssetManager, but we still need to localize the script
1939 $this->localize_editor_script();
1940 }
1941
1942 /**
1943 * Localize editor script with necessary data
1944 * Note: Localization is now handled by LocalizationManager
1945 */
1946 private function localize_editor_script()
1947 {
1948 // Localization is now handled by LocalizationManager
1949 // This method is kept for backward compatibility but functionality has been moved
1950 }
1951
1952 /**
1953 * Add a new block to the available blocks list
1954 */
1955 public function add_block($folder_name, $config)
1956 {
1957 $this->available_blocks[$folder_name] = $config;
1958 }
1959
1960 /**
1961 * The Gutenberg blocks that ship enabled by DEFAULT. Single source of
1962 * truth, used both to seed a fresh install AND to self-heal existing
1963 * installs when a new default block ships in a later release (see
1964 * register_blocks()). Add a new default-on block's setting_key here.
1965 */
1966 private function default_enabled_blocks()
1967 {
1968 return [
1969 'embedpress',
1970 'embedpress-pdf',
1971 'youtube-block',
1972 'wistia-block',
1973 'pdf-gallery',
1974 'google-reviews',
1975 ];
1976 }
1977
1978 /**
1979 * Ensure the default-on blocks are enabled. Idempotent — only adds keys that
1980 * are MISSING, never flips a key the user explicitly turned off (an existing
1981 * key, even value '', is left untouched). Safe to call on every load; it
1982 * only persists when something actually changed. This is what makes a block
1983 * added in a later release (google-reviews, pdf-gallery, …) appear on
1984 * existing installs after an update instead of staying invisible.
1985 */
1986 private function ensure_default_blocks_enabled()
1987 {
1988 $elements = (array) get_option(EMBEDPRESS_PLG_NAME . ":elements", []);
1989
1990 if (!isset($elements['gutenberg'])) {
1991 $elements['gutenberg'] = [];
1992 }
1993
1994 $changed = false;
1995 foreach ($this->default_enabled_blocks() as $key) {
1996 // array_key_exists (not isset) so a user-disabled block stored as ''
1997 // is respected — we only add a key that was never present at all.
1998 if (!array_key_exists($key, $elements['gutenberg'])) {
1999 $elements['gutenberg'][$key] = $key;
2000 $changed = true;
2001 }
2002 }
2003
2004 if ($changed) {
2005 update_option(EMBEDPRESS_PLG_NAME . ":elements", $elements);
2006 }
2007 }
2008
2009 /**
2010 * Get available blocks
2011 */
2012 public function get_available_blocks()
2013 {
2014 return $this->available_blocks;
2015 }
2016 }
2017