PluginProbe
Extendify / 3.1.4
Extendify v3.1.4
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 / QuickEdit / Schemas / MediaText.php

MediaText.php in Extendify 3.1.4, at app/QuickEdit/Schemas/MediaText.php

185 lines 7.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Extendify\QuickEdit\Schemas;
4
5 defined('ABSPATH') || die('No direct access.');
6
7 // Mirrors the media-* attrs and the inner <figure>/<img> markup so the
8 // parsed-block tree stays internally consistent across re-renders. The
9 // editable target is the image on the media side; the synthetic client
10 // blockType `core/media-text:image` resolves to the real `core/media-text`
11 // before it reaches SaveController, which is why this is keyed on the real
12 // block name in the Registry.
13 class MediaText implements Schema
14 {
15 public function fields(): array
16 {
17 return [
18 [
19 'key' => 'media',
20 'control' => 'image',
21 'label' => __('Image', 'extendify-local'),
22 ],
23 ];
24 }
25
26 public function apply(array $block, string $fieldKey, $value): array
27 {
28 if ($fieldKey !== 'media' || !is_array($value)) {
29 return $block;
30 }
31
32 $id = isset($value['id']) ? (int) $value['id'] : null;
33 $alt = isset($value['alt']) ? wp_strip_all_tags((string) $value['alt']) : null;
34
35 // A positive id must resolve to a real image attachment; trust the
36 // server's URL when it does. The raw client URL is only honored for
37 // the id-less external-image case (mirror of Image::apply).
38 if ($id !== null && $id > 0) {
39 if (!wp_attachment_is_image($id)) {
40 return $block;
41 }
42 $url = wp_get_attachment_image_url($id, 'full') ?: null;
43 } else {
44 $url = isset($value['url']) ? esc_url_raw((string) $value['url']) : null;
45 }
46
47 if (!$url) {
48 return $block;
49 }
50
51 $attrs = $block['attrs'] ?? [];
52 $attrs['mediaUrl'] = $url;
53 $attrs['mediaLink'] = $url;
54 $attrs['mediaType'] = 'image';
55 if ($id !== null && $id > 0) {
56 $attrs['mediaId'] = $id;
57 } else {
58 unset($attrs['mediaId']);
59 }
60 if ($alt !== null) {
61 $attrs['mediaAlt'] = $alt;
62 }
63 $attrs = $this->dropImportClass($attrs);
64 $block['attrs'] = $attrs;
65
66 // Static block: serialize_blocks emits the stored innerContent verbatim,
67 // so the rendered <img>/background must be patched here — there's no
68 // save() re-run from attrs. The media <figure> + <img> live in the
69 // leading string chunk (the content side is inner blocks → nulls), so
70 // find that chunk and patch in place. NOT innerHTML, whose nulls would
71 // collapse and drop the content-side inner blocks (see Cover::apply).
72 $innerContent = $block['innerContent'] ?? [];
73 foreach ($innerContent as $i => $chunk) {
74 if (!is_string($chunk) || strpos($chunk, 'wp-block-media-text__media') === false) {
75 continue;
76 }
77 $tp = new \WP_HTML_Tag_Processor($chunk);
78 $inMedia = false;
79 $patched = false;
80 while ($tp->next_tag()) {
81 $tag = $tp->get_tag();
82 if ($tag === 'FIGURE') {
83 $inMedia = $tp->has_class('wp-block-media-text__media');
84 if ($inMedia) {
85 // imageFill renders the media as the figure's CSS
86 // background; keep that url in sync with the <img>.
87 $style = $tp->get_attribute('style');
88 if (is_string($style) && stripos($style, 'background-image') !== false) {
89 $tp->set_attribute('style', preg_replace_callback(
90 '/background-image\s*:\s*url\([^)]*\)/i',
91 static function () use ($url) {
92 return 'background-image:url(' . $url . ')';
93 },
94 $style
95 ));
96 $patched = true;
97 }
98 }
99 continue;
100 }
101 if ($tag === 'IMG' && $inMedia) {
102 $tp->set_attribute('src', $url);
103 if ($alt !== null) {
104 $tp->set_attribute('alt', $alt);
105 }
106
107 // Gutenberg's media-text save() derives wp-image-{id} from
108 // attrs.mediaId; a mismatch trips the validator. Strip any
109 // pre-existing first so re-edits don't stack.
110 $cls = (string) ($tp->get_attribute('class') ?? '');
111 $cls = trim((string) preg_replace('/\bwp-image-\d+\b/', '', $cls));
112 if ($id !== null && $id > 0) {
113 $cls = trim($cls . ' wp-image-' . $id);
114 }
115 $cls = (string) preg_replace('/\s{2,}/', ' ', $cls);
116 if ($cls === '') {
117 $tp->remove_attribute('class');
118 } else {
119 $tp->set_attribute('class', $cls);
120 }
121
122 if ($id === null || $id <= 0) {
123 $tp->remove_attribute('srcset');
124 $tp->remove_attribute('sizes');
125 }
126 $patched = true;
127 break;
128 }
129 }
130 if ($patched) {
131 $patchedChunk = $this->stripImportMarker($tp->get_updated_html(), 'div');
132 $innerContent[$i] = $patchedChunk;
133 $block['innerContent'] = $innerContent;
134 // Inspection-only; the serializer rebuilds innerHTML from innerContent.
135 $block['innerHTML'] = implode('', array_map(
136 static function ($c) {
137 return is_string($c) ? $c : '';
138 },
139 $innerContent
140 ));
141 break;
142 }
143 }
144
145 return $block;
146 }
147
148 // The Launch-import marker lives in attrs.className for media-text (it
149 // renders into the wrapper div); strip it so a Quick Edit replace claims
150 // the image the same way the agent's change-image flow does.
151 private function dropImportClass(array $attrs): array
152 {
153 if (!isset($attrs['className']) || !is_string($attrs['className'])) {
154 return $attrs;
155 }
156 $classes = array_filter(
157 preg_split('/\s+/', $attrs['className']) ?: [],
158 static function ($c) {
159 return $c !== '' && $c !== 'extendify-image-import';
160 }
161 );
162 if ($classes) {
163 $attrs['className'] = implode(' ', $classes);
164 } else {
165 unset($attrs['className']);
166 }
167 return $attrs;
168 }
169
170 // Mirror of Image::stripImportMarker — drop the marker class that survives
171 // in the rendered wrapper but not in attrs after the className edit above.
172 private function stripImportMarker(string $html, string $tagName): string
173 {
174 $tp = new \WP_HTML_Tag_Processor($html);
175 if (!$tp->next_tag($tagName)) {
176 return $html;
177 }
178 if (!$tp->has_class('extendify-image-import')) {
179 return $html;
180 }
181 $tp->remove_class('extendify-image-import');
182 return $tp->get_updated_html();
183 }
184 }
185