PluginProbe
Extendify / 3.1.0
Extendify v3.1.0
3.2.1 3.2.0 3.1.6 3.1.5 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.6 3.0.5 3.0.4 trunk 0.1.0 0.10.0 0.10.1 0.10.2 0.11.0 0.11.1 0.2.0 0.3.0 0.3.1 0.4.0 0.5.0 0.6.0 All 127 releases
extendify / app / Agent / Controllers / WPController.php

WPController.php in Extendify 3.1.0, at app/Agent/Controllers/WPController.php

828 lines 27.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * WP Controller
5 */
6
7 namespace Extendify\Agent\Controllers;
8
9 defined('ABSPATH') || die('No direct access.');
10
11 use Extendify\Constants;
12 use Extendify\Shared\Services\Sanitizer;
13
14 /**
15 * The controller for interacting with WordPress.
16 */
17
18 class WPController
19 {
20 /**
21 * $ignoredKeys are only removed top-level (line 94) and not recursively
22 *
23 * @var string[]
24 */
25 public static $ignoredKeys = [
26 'title',
27 '$schema',
28 'version',
29 'slug',
30 ];
31 /**
32 * Allowed variations for the extendable theme
33 *
34 * @var string[]
35 */
36 public static $allowedVariationsList = [
37 'bloom',
38 'brick',
39 'cobalt',
40 'coral',
41 'evergreen',
42 'gold',
43 'lilac',
44 'lime',
45 'midnight',
46 'moss',
47 'neon',
48 'rosewood',
49 'slate',
50 'onyx',
51 'glasgow',
52 'royal',
53 'obsidian',
54 ];
55
56 /**
57 * Recursively filter an array to include only specified properties.
58 *
59 * This function traverses the array structure and retains only the properties
60 * specified in the allowed keys, preserving the original hierarchical structure.
61 * Keys that don't match the allowed set are excluded from the result.
62 *
63 * @param array $data The input array to filter
64 * @param array $allowedKeys Associative array of allowed property keys (keys as indices)
65 * @return array Filtered array containing only allowed properties, maintaining structure
66 */
67 protected static function filterArrayByProperties(array $data, array $allowedKeys)
68 {
69 if (empty($allowedKeys) || empty($data)) {
70 return [];
71 }
72
73 $result = [];
74 foreach ($data as $key => $value) {
75 if (isset($allowedKeys[$key])) {
76 $result[$key] = $value;
77 } elseif (is_array($value)) {
78 // Recursively filter nested arrays
79 $filtered = self::filterArrayByProperties($value, $allowedKeys);
80 if (!empty($filtered)) {
81 $result[$key] = $filtered;
82 }
83 }
84 }
85 return $result;
86 }
87
88 /**
89 * Validates if a variation contains only specified properties.
90 *
91 * This function checks whether the variation array contains exclusively the
92 * specified properties throughout its entire hierarchy.
93 *
94 * @param array $variation The theme variation arrays to validate
95 * @param array $allowedKeys List of property names that should be the only ones present
96 * @return bool TRUE if only specified properties exist, FALSE otherwise
97 */
98 protected static function variationHasProperties(array $variation, array $allowedKeys)
99 {
100 if (empty($variation) || empty($allowedKeys)) {
101 return false;
102 }
103
104 $allowedKeys = array_flip($allowedKeys);
105 $data = array_diff_key($variation, array_flip(self::$ignoredKeys));
106 $filtered = self::filterArrayByProperties($data, $allowedKeys);
107
108 return serialize($filtered) === serialize($data);
109 }
110
111 /**
112 * Get the CSS for each variation.
113 *
114 * @param array $variations The theme variations to process.
115 * @param \WP_Theme_JSON $current The current theme JSON data.
116 * @param bool $includeLayoutStyles Whether to include layout styles in the CSS.
117 * @return array The variations with their corresponding CSS.
118 */
119 protected static function getCss($variations, $current, $includeLayoutStyles)
120 {
121 $deduped = [];
122 foreach ($variations as $variation) {
123 $title = $variation['title'] ?? null;
124 if (!$title || isset($deduped[$title])) {
125 continue;
126 }
127 $theme = new \WP_Theme_JSON();
128 $theme->merge($current);
129 $theme->merge(new \WP_Theme_JSON($variation));
130 $css = $theme->get_stylesheet(
131 ["variables", "styles", "presets"],
132 null,
133 ["skip_root_layout_styles" => !$includeLayoutStyles, 'include_block_style_variations' => true]
134 );
135 $variation['css'] = $css;
136 // to make sure we exit early
137 $deduped[$title] = $variation;
138 }
139
140 return array_values($deduped);
141 }
142
143 /**
144 * Get Theme Variations and the compiled CSS for each variation.
145 *
146 * @param \WP_REST_Request $request The REST API request object.
147 * @return \WP_REST_Response
148 */
149 public static function getVariations($request)
150 {
151 $includeLayoutStyles = $request->has_param('includeLayoutStyles');
152 $current = \WP_Theme_JSON_Resolver::get_merged_data();
153 $unfiltered = \WP_Theme_JSON_Resolver::get_style_variations();
154
155 $variations = array_filter($unfiltered, function ($variation) {
156 return self::variationHasProperties($variation, ['color']);
157 });
158
159 $buildSlugMap = function ($unfiltered) {
160 $slugMap = [];
161
162 if (!is_array($unfiltered)) {
163 return $slugMap;
164 }
165
166 foreach ($unfiltered as $rawSlug => $rawVariation) {
167 $title = is_array($rawVariation) ? ($rawVariation['title'] ?? null) : null;
168 $slug = is_array($rawVariation)
169 ? ($rawVariation['slug'] ?? (is_string($rawSlug) ? $rawSlug : null))
170 : null;
171
172 if ($title && $slug && !isset($slugMap[$title])) {
173 $slugMap[$title] = $slug;
174 }
175 }
176 return $slugMap;
177 };
178 $slugMap = $buildSlugMap($unfiltered);
179 array_walk($variations, function (&$variation) use ($slugMap) {
180 if (!is_array($variation) || isset($variation['slug'])) {
181 return;
182 }
183
184 $title = $variation['title'] ?? null;
185 if ($title && isset($slugMap[$title])) {
186 $variation['slug'] = $slugMap[$title];
187 }
188 });
189
190 $deduped = static::getCss($variations, $current, $includeLayoutStyles);
191 // if the theme is extendable we need to filter the variations using the allowed variations list
192 if (\get_option('stylesheet') === 'extendable') {
193 $deduped = array_filter($deduped, function ($variation) {
194 return in_array($variation['slug'], self::$allowedVariationsList);
195 });
196 }
197
198
199 return new \WP_REST_Response(array_values($deduped));
200 }
201
202 /**
203 * Get Theme fonts Variations and the compiled CSS for each variation.
204 *
205 * @param \WP_REST_Request $request The REST API request object.
206 * @return \WP_REST_Response
207 */
208 public static function getFontsVariations($request)
209 {
210 $includeLayoutStyles = $request->has_param('includeLayoutStyles');
211 $current = \WP_Theme_JSON_Resolver::get_merged_data();
212 $unfiltered = \WP_Theme_JSON_Resolver::get_style_variations();
213
214 $fontsVariations = array_filter($unfiltered, function ($variation) {
215 return self::variationHasProperties($variation, ['elements', 'typography']);
216 });
217
218 $processedFonts = array_map(function ($variation) {
219 if (!isset($variation['styles']['elements']) || !is_array($variation['styles']['elements'])) {
220 return $variation;
221 }
222
223 $variation['styles']['elements'] = array_map(
224 [self::class, 'normalizeElementTypography'],
225 $variation['styles']['elements']
226 );
227
228 if (!isset($variation['styles']['typography'])) {
229 $variation['styles']['typography'] = [
230 'fontFamily' => 'var(--wp--preset--font-family--inter)'
231 ];
232 }
233
234 // Removing the settings that cause the style to change.
235 unset($variation['settings']);
236
237 return $variation;
238 }, $fontsVariations);
239
240 $deduped = static::getCss($processedFonts, $current, $includeLayoutStyles);
241 return new \WP_REST_Response($deduped);
242 }
243
244 /**
245 * Get block style variations (vibes) from merged global styles
246 *
247 * @param \WP_REST_Request $request The request.
248 * @return \WP_REST_Response
249 */
250 public static function getBlockStyleVariations($request)
251 {
252 // Get theme + DB merged Global Styles
253 $merged = wp_get_global_styles();
254 $blocks = $merged['blocks'] ?? [];
255
256 $variations = [];
257
258 foreach ($blocks as $blockName => $blockData) {
259 if (!isset($blockData['variations'])) {
260 continue;
261 }
262
263 $variations[$blockName] = $blockData['variations'];
264 }
265
266 return new \WP_REST_Response($variations, 200);
267 }
268
269 /**
270 * Normalize typography properties for theme element styles.
271 *
272 * @param array $elementStyles The element styles array containing typography configuration
273 * @return array Normalized typography properties with filtered null values
274 */
275 protected static function normalizeElementTypography(array $elementStyles)
276 {
277 $typography = $elementStyles['typography'] ?? [];
278
279 return [
280 'typography' => array_filter([
281 'fontFamily' => $typography['fontFamily'] ?? null,
282 'fontSize' => $typography['fontSize'] ?? null,
283 'lineHeight' => $typography['lineHeight'] ?? null,
284 'letterSpacing' => $typography['letterSpacing'] ?? null,
285 'fontStyle' => $typography['fontStyle'] ?? null,
286 'fontWeight' => $typography['fontWeight'] ?? null,
287 'textTransform' => $typography['textTransform'] ?? 'none',
288 ], function ($v) {
289 return $v !== null;
290 })
291 ];
292 }
293
294
295 /**
296 * Get the HTML of a specific tagged block code
297 *
298 * @param \WP_REST_Request $request The REST API request object.
299 * @return \WP_REST_Response
300 */
301 public static function getBlockCode(\WP_REST_Request $request)
302 {
303 $blockId = (int) $request->get_param('blockId');
304
305 if ($blockId < 1) {
306 return new \WP_REST_Response(['error' => 'Invalid blockId'], 400);
307 }
308
309 // A template-part source resolves the part and walks it with the shared
310 // preorder finder, instead of the post path below.
311 $partSlug = (string) $request->get_param('partSlug');
312 if ($partSlug !== '') {
313 return self::getTemplatePartBlockCode($partSlug, $blockId);
314 }
315
316 $postId = (int) $request->get_param('postId');
317 $post = \get_post($postId);
318 if (!$post) {
319 return new \WP_REST_Response(['error' => 'Post not found'], 404);
320 }
321
322 $ignored = \Extendify\Agent\TagBlocks::$ignored;
323
324 $ast = array_values(array_filter(
325 parse_blocks($post->post_content),
326 static function ($b) {
327 return is_array($b) && !empty($b['blockName']);
328 }
329 ));
330
331 $seq = 0;
332 $found = null;
333
334 $walk = function (array $list) use (&$walk, &$seq, $blockId, &$found, $ignored) {
335 foreach ($list as $b) {
336 $name = $b['blockName'] ?? null;
337 if (!$name) {
338 continue;
339 }
340
341 // Ignore this block and its subtree (matches tagger behavior)
342 if (in_array($name, $ignored, true)) {
343 continue; // do NOT increment seq, do NOT traverse children
344 }
345
346 $seq++;
347 if ($seq === $blockId) {
348 $found = $b;
349 return true;
350 }
351
352 if (!empty($b['innerBlocks']) && $walk($b['innerBlocks'])) {
353 return true;
354 }
355 }
356 return false;
357 };
358 $walk($ast);
359
360 if (!is_array($found) || empty($found['blockName'])) {
361 return new \WP_REST_Response(['error' => 'Block id not found in this post'], 404);
362 }
363
364 return new \WP_REST_Response([
365 'postId' => $postId,
366 'blockId' => $blockId,
367 'name' => $found['blockName'],
368 'attrs' => $found['attrs'] ?? (object)[],
369 'block' => serialize_blocks([$found]),
370 ], 200);
371 }
372
373 // Resolves the part the same way SaveController does — active-theme-scoped
374 // get_block_template, not a raw get_posts by name — then walks it with the
375 // shared preorder finder so the blockId lands on the block save will write.
376 private static function getTemplatePartBlockCode(string $slug, int $blockId)
377 {
378 $stylesheet = wp_get_theme()->get_stylesheet();
379 $template = get_block_template("{$stylesheet}//{$slug}", 'wp_template_part');
380 if (!$template || empty($template->wp_id)) {
381 return new \WP_REST_Response(['error' => 'Template part not found'], 404);
382 }
383
384 $post = \get_post($template->wp_id);
385 if (!$post) {
386 return new \WP_REST_Response(['error' => 'Template part not found'], 404);
387 }
388
389 $found = \Extendify\Agent\TemplatePartBlockFinder::find(
390 parse_blocks($post->post_content),
391 $blockId
392 );
393 if (!is_array($found) || empty($found['block']['blockName'])) {
394 return new \WP_REST_Response(['error' => 'Block id not found in this template part'], 404);
395 }
396
397 $block = $found['block'];
398 return new \WP_REST_Response([
399 'partSlug' => $slug,
400 'blockId' => $blockId,
401 'name' => $block['blockName'],
402 'attrs' => $block['attrs'] ?? (object)[],
403 'block' => serialize_blocks([$block]),
404 ], 200);
405 }
406
407 /**
408 * Get the rendered HTML of some block code
409 *
410 * @param \WP_REST_Request $request The REST API request object.
411 * @return \WP_REST_Response
412 */
413 public static function getBlockHtml($request)
414 {
415 $blockCode = $request->get_param('blockCode');
416 $content = \do_blocks($blockCode);
417
418 return new \WP_REST_Response(['content' => trim($content)]);
419 }
420
421 /**
422 * Get the Hero Patterns from the API
423 *
424 * @param string $title The title to replace in the pattern code
425 * @param string $description The description to replace in the pattern code
426 * @param array $images The images to replace in the pattern code, as an array of urls
427 * @param array $cta The cta to replace in the pattern code, as an array with 'label' and 'link' keys
428 * @param bool $featuredOnly Whether to limit to featured patterns
429 * @param string|null $source What triggered the request, e.g. 'change-site-design-workflow'
430 * @return array|\WP_Error|array<string|int, mixed> The hero patterns data or a WP_Error on failure
431 */
432 protected static function getHeroPatternsData(
433 $title,
434 $description,
435 $images,
436 $cta,
437 $featuredOnly = false,
438 $source = null
439 ) {
440 $response = \wp_remote_post(
441 Constants::PATTERNS_HOST . '/api/heros',
442 [
443 'headers' => [
444 'Content-Type' => 'application/json',
445 'Accept' => 'application/json',
446 ],
447 'body' => wp_json_encode([
448 "wpVersion" => \get_bloginfo('version'),
449 "wpLanguage" => \get_locale(),
450 "featured" => $featuredOnly,
451 "source" => $source,
452 ])
453 ]
454 );
455
456 if (\is_wp_error($response)) {
457 return $response;
458 }
459
460 $body = json_decode(\wp_remote_retrieve_body($response), true);
461
462 $cursor = 0;
463 $imageCount = count($images);
464 $heroPatterns = [];
465
466 foreach ($body as $heroPattern) {
467 $code = $heroPattern['code'] ?? '';
468
469 if ($title) {
470 $code = preg_replace(
471 '/(<!-- wp:heading[^>]*-->[\s\S]*?<h1[^>]*>)[\s\S]*?(<\/h1>[\s\S]*?<!-- \/wp:heading -->)/m',
472 '${1}' . esc_html($title) . '${2}',
473 $code,
474 1
475 );
476 }
477
478 if ($description) {
479 $code = preg_replace(
480 '/(<!-- wp:paragraph[^>]*-->[\s\S]*?<p[^>]*>)[\s\S]*?(<\/p>[\s\S]*?<!-- \/wp:paragraph -->)/m',
481 '${1}' . esc_html($description) . '${2}',
482 $code,
483 1
484 );
485 }
486
487 if ($cta['label'] ?? null) {
488 $code = preg_replace(
489 '/(<!-- wp:button[^>]*-->[\s\S]*?<a[^>]*>)[\s\S]*?(<\/a>[\s\S]*?<!-- \/wp:button -->)/m',
490 '${1}' . esc_html($cta['label']) . '${2}',
491 $code,
492 1
493 );
494 }
495
496 if ($cta['link'] ?? null) {
497 $code = preg_replace(
498 '/(<!-- wp:button[\s\S]*?<a[^>]*\shref=")[^"]*(")/m',
499 '${1}' . esc_url($cta['link']) . '${2}',
500 $code,
501 1
502 );
503 }
504
505 $patternUrls = $heroPattern['urls'] ?? [];
506 foreach ($patternUrls as $key => $url) {
507 if (!$imageCount) {
508 break;
509 }
510 $code = str_replace($url, $images[($cursor + $key) % $imageCount], $code);
511 }
512 $cursor = $imageCount ? ($cursor + count($patternUrls)) % $imageCount : 0;
513
514 $renderedHtml = do_blocks(str_replace('ext-animate--on', '', $code));
515
516 $blockSupportsCss = function_exists('wp_style_engine_get_stylesheet_from_context')
517 ? wp_style_engine_get_stylesheet_from_context('block-supports')
518 : '';
519
520 // Clear block-supports store so CSS doesn't accumulate across patterns.
521 \WP_Style_Engine_CSS_Rules_Store::remove_all_stores();
522
523 $linkStyles = array_values(
524 array_filter(
525 array_map(
526 function ($style) {
527 return wp_styles()->registered[$style]->src ?? null;
528 },
529 wp_styles()->queue ?? []
530 )
531 )
532 );
533
534 /**
535 * Clear queue for the next pattern.
536 *
537 * `do_blocks` appends to the global queue the styles needed for the blocks.
538 */
539 wp_styles()->queue = [];
540
541 $heroPatterns[] = [
542 'id' => $heroPattern['id'],
543 'name' => $heroPattern['name'],
544 'code' => $code,
545 'renderedHtml' => $renderedHtml,
546 'blockSupportsCss' => $blockSupportsCss,
547 'linkStyles' => $linkStyles,
548 ];
549 }
550
551 return $heroPatterns;
552 }
553
554 /**
555 * Get the Hero Patterns
556 *
557 * @param \WP_REST_Request $request The REST API request object.
558 * @return \WP_REST_Response
559 */
560 public static function getHeroPatterns(\WP_REST_Request $request)
561 {
562 $title = $request->get_param('title');
563 $description = $request->get_param('description');
564 $images = $request->get_param('images');
565 $cta = $request->get_param('cta');
566
567 $heroPatterns = self::getHeroPatternsData($title, $description, $images, $cta);
568
569 if (\is_wp_error($heroPatterns)) {
570 return new \WP_REST_Response([], 500);
571 }
572
573 $blockEditorContext = new \WP_Block_Editor_Context(array( 'name' => 'core/edit-post' ));
574 $editorSettings = get_block_editor_settings([], $blockEditorContext);
575
576 return new \WP_REST_Response(['patterns' => $heroPatterns, 'blockEditorSettings' => $editorSettings ?? null]);
577 }
578
579 /**
580 * Build the image list for the hero section.
581 *
582 * Returns up to 6 images: unused site images first, already-used images last.
583 * When already at capacity, reverses the array so the last-used image leads next time.
584 */
585 protected static function getHeroSectionImages(array $images, array $siteImages, int $postId): array
586 {
587 $maxSlots = 6;
588
589 if (count($images) >= $maxSlots) {
590 return array_reverse($images);
591 }
592
593 if (!$postId || empty($siteImages)) {
594 return $images;
595 }
596
597 $usedImages = self::resolveUsedImages($postId);
598 $siteImages = array_map(function ($url) {
599 return self::stripQueryString($url);
600 }, $siteImages);
601
602 $unusedSiteImages = array_values(array_filter(
603 $siteImages,
604 function ($url) use ($usedImages, $images) {
605 return !in_array($url, $usedImages, true) && !in_array($url, $images, true);
606 }
607 ));
608
609 $unusedSlots = $maxSlots - count($images);
610 return array_merge(array_slice($unusedSiteImages, 0, $unusedSlots), $images);
611 }
612
613 protected static function stripQueryString(string $url): string
614 {
615 $parsed = wp_parse_url($url);
616 return ($parsed['scheme'] ?? 'https') . '://' . ($parsed['host'] ?? '') . ($parsed['path'] ?? '');
617 }
618
619 /**
620 * Get all Unsplash URLs used in a post.
621 */
622 protected static function resolveUsedImages(int $postId): array
623 {
624 $post = get_post($postId);
625 if (!$post) {
626 return [];
627 }
628
629 $processor = new \WP_HTML_Tag_Processor($post->post_content);
630 $usedImages = [];
631
632 while ($processor->next_tag('img')) {
633 $src = $processor->get_attribute('src');
634 if (!$src) {
635 continue;
636 }
637
638 $baseUrl = self::stripQueryString($src);
639
640 if (str_contains($src, 'unsplash.com')) {
641 $usedImages[] = $baseUrl;
642 continue;
643 }
644
645 $attachmentId = attachment_url_to_postid(esc_url($baseUrl));
646
647 if (!$attachmentId) {
648 continue;
649 }
650
651 $sourceUrl = get_post_meta($attachmentId, '_extendify_source_url', true);
652
653 if (!$sourceUrl) {
654 continue;
655 }
656
657 $usedImages[] = self::stripQueryString($sourceUrl);
658 }
659
660 return array_values(array_unique($usedImages));
661 }
662
663 public static function getSiteDesignVariations(\WP_REST_Request $request)
664 {
665 $title = $request->get_param('title');
666 $description = $request->get_param('description');
667 $images = $request->get_param('images') ?? [];
668 $cta = $request->get_param('cta');
669 $featuredOnly = true; // Only show featured patterns
670 $currentHeroPattern = $request->get_param('currentHeroPattern');
671 $postId = (int) $request->get_param('postId');
672 $siteImages = $request->get_param('siteImages') ?? [];
673 $source = $request->get_param('source');
674
675 $images = self::getHeroSectionImages($images, $siteImages, $postId);
676
677 $heroPatterns = self::getHeroPatternsData($title, $description, $images, $cta, $featuredOnly, $source);
678
679 if (\is_wp_error($heroPatterns)) {
680 return new \WP_REST_Response([], 500);
681 }
682
683 $heroPatterns = array_values(
684 array_filter(
685 $heroPatterns,
686 function ($heroPattern) use ($currentHeroPattern) {
687 if (!$currentHeroPattern) {
688 return true;
689 }
690
691 return $heroPattern['name'] !== $currentHeroPattern;
692 }
693 )
694 );
695
696 $current = \WP_Theme_JSON_Resolver::get_merged_data('theme');
697
698 $unfiltered = \WP_Theme_JSON_Resolver::get_style_variations();
699
700 // Keep only full style variations — exclude color-only and font-only
701 // presets that get_style_variations() returns from styles/colors/* and
702 // styles/typography/*.
703 $colorAndFontsVariations = array_filter($unfiltered, function ($variation) {
704 $hasPalette = ($variation['settings']['color']['palette'] ?? []) !== [];
705 $hasTypography = ($variation['styles']['typography'] ?? []) !== []
706 || ($variation['settings']['typography'] ?? []) !== [];
707 $hasElements = ($variation['styles']['elements'] ?? []) !== [];
708 return $hasPalette && $hasTypography && $hasElements;
709 });
710
711 $buildSlugMap = function ($unfiltered) {
712 $slugMap = [];
713
714 if (!is_array($unfiltered)) {
715 return $slugMap;
716 }
717
718 foreach ($unfiltered as $rawSlug => $rawVariation) {
719 $title = is_array($rawVariation) ? ($rawVariation['title'] ?? null) : null;
720 $slug = is_array($rawVariation)
721 ? ($rawVariation['slug'] ?? (is_string($rawSlug) ? $rawSlug : null))
722 : null;
723
724 if ($title && $slug && !isset($slugMap[$title])) {
725 $slugMap[$title] = $slug;
726 }
727 }
728 return $slugMap;
729 };
730 $slugMap = $buildSlugMap($unfiltered);
731 array_walk($colorAndFontsVariations, function (&$variation) use ($slugMap) {
732 if (!is_array($variation) || isset($variation['slug'])) {
733 return;
734 }
735
736 $title = $variation['title'] ?? null;
737 if ($title && isset($slugMap[$title])) {
738 $variation['slug'] = $slugMap[$title];
739 }
740 });
741
742 $processedFonts = array_map(function ($variation) {
743 if (!isset($variation['styles']['elements']) || !is_array($variation['styles']['elements'])) {
744 return $variation;
745 }
746
747 $variation['styles']['elements'] = array_map(
748 [self::class, 'normalizeElementTypography'],
749 $variation['styles']['elements']
750 );
751
752 if (!isset($variation['styles']['typography'])) {
753 $variation['styles']['typography'] = [
754 'fontFamily' => 'var(--wp--preset--font-family--inter)'
755 ];
756 }
757
758 return $variation;
759 }, $colorAndFontsVariations);
760
761 $deduped = static::getCss($processedFonts, $current, true);
762
763 $blockEditorContext = new \WP_Block_Editor_Context(array( 'name' => 'core/edit-post' ));
764 $editorSettings = get_block_editor_settings([], $blockEditorContext);
765 $editorSettings['styles'] = [];
766
767 return new \WP_REST_Response(
768 [
769 'patterns' => $heroPatterns,
770 'colorAndFontsVariations' => $deduped,
771 'blockEditorSettings' => $editorSettings ?? null,
772 ]
773 );
774 }
775
776 /**
777 * Sets a lock on a post to prevent concurrent editing.
778 *
779 * @param \WP_REST_Request $request The REST API request object containing postId.
780 * @return \WP_REST_Response Response indicating success of the lock operation.
781 */
782 public static function lockPost($request)
783 {
784 $postId = (int) $request->get_param('postId');
785 require_once ABSPATH . '/wp-admin/includes/post.php';
786 $data = \wp_set_post_lock($postId);
787 return new \WP_REST_Response(['success' => $data !== false]);
788 }
789
790 /**
791 * Persist the data
792 *
793 * @param \WP_REST_Request $request - The request.
794 * @return \WP_REST_Response
795 */
796 public static function updateOption($request)
797 {
798 $params = $request->get_json_params();
799 $key = $params['option'];
800 $sanitized = Sanitizer::sanitizeUnknown($params['value']);
801
802 if (strpos($key, 'extendify_') === 0) {
803 $key = substr($key, 10);
804 }
805 \update_option('extendify_' . $key, $sanitized);
806
807 return new \WP_REST_Response('OK');
808 }
809
810 /**
811 * Get the data
812 *
813 * @param \WP_REST_Request $request - The request.
814 * @return \WP_REST_Response
815 */
816 public static function getOption($request)
817 {
818 $key = $request->get_param('option');
819
820 if (strpos($key, 'extendify_') === 0) {
821 $key = substr($key, 10);
822 }
823 $value = \get_option('extendify_' . $key, null);
824
825 return new \WP_REST_Response($value);
826 }
827 }
828