PluginProbe
Extendify / 3.1.3
Extendify v3.1.3
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.3, at app/QuickEdit/Schemas/MediaText.php

179 lines 7.0 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 fn () => 'background-image:url(' . $url . ')',
92 $style
93 ));
94 $patched = true;
95 }
96 }
97 continue;
98 }
99 if ($tag === 'IMG' && $inMedia) {
100 $tp->set_attribute('src', $url);
101 if ($alt !== null) {
102 $tp->set_attribute('alt', $alt);
103 }
104
105 // Gutenberg's media-text save() derives wp-image-{id} from
106 // attrs.mediaId; a mismatch trips the validator. Strip any
107 // pre-existing first so re-edits don't stack.
108 $cls = (string) ($tp->get_attribute('class') ?? '');
109 $cls = trim((string) preg_replace('/\bwp-image-\d+\b/', '', $cls));
110 if ($id !== null && $id > 0) {
111 $cls = trim($cls . ' wp-image-' . $id);
112 }
113 $cls = (string) preg_replace('/\s{2,}/', ' ', $cls);
114 if ($cls === '') {
115 $tp->remove_attribute('class');
116 } else {
117 $tp->set_attribute('class', $cls);
118 }
119
120 if ($id === null || $id <= 0) {
121 $tp->remove_attribute('srcset');
122 $tp->remove_attribute('sizes');
123 }
124 $patched = true;
125 break;
126 }
127 }
128 if ($patched) {
129 $patchedChunk = $this->stripImportMarker($tp->get_updated_html(), 'div');
130 $innerContent[$i] = $patchedChunk;
131 $block['innerContent'] = $innerContent;
132 // Inspection-only; the serializer rebuilds innerHTML from innerContent.
133 $block['innerHTML'] = implode('', array_map(
134 static fn ($c) => is_string($c) ? $c : '',
135 $innerContent
136 ));
137 break;
138 }
139 }
140
141 return $block;
142 }
143
144 // The Launch-import marker lives in attrs.className for media-text (it
145 // renders into the wrapper div); strip it so a Quick Edit replace claims
146 // the image the same way the agent's change-image flow does.
147 private function dropImportClass(array $attrs): array
148 {
149 if (!isset($attrs['className']) || !is_string($attrs['className'])) {
150 return $attrs;
151 }
152 $classes = array_filter(
153 preg_split('/\s+/', $attrs['className']) ?: [],
154 static fn ($c) => $c !== '' && $c !== 'extendify-image-import'
155 );
156 if ($classes) {
157 $attrs['className'] = implode(' ', $classes);
158 } else {
159 unset($attrs['className']);
160 }
161 return $attrs;
162 }
163
164 // Mirror of Image::stripImportMarker — drop the marker class that survives
165 // in the rendered wrapper but not in attrs after the className edit above.
166 private function stripImportMarker(string $html, string $tagName): string
167 {
168 $tp = new \WP_HTML_Tag_Processor($html);
169 if (!$tp->next_tag($tagName)) {
170 return $html;
171 }
172 if (!$tp->has_class('extendify-image-import')) {
173 return $html;
174 }
175 $tp->remove_class('extendify-image-import');
176 return $tp->get_updated_html();
177 }
178 }
179