PluginProbe
Extendify / 3.1.5
Extendify v3.1.5
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.5, at app/QuickEdit/Schemas/MediaText.php

182 lines 7.1 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 // Core's save() derives wp-image-{id} + size-{mediaSizeSlug||full}
108 // from attrs; mismatched markup trips the block validator.
109 foreach ($tp->class_list() as $class) {
110 if (str_starts_with($class, 'wp-image-') || str_starts_with($class, 'size-')) {
111 $tp->remove_class($class);
112 }
113 }
114 if ($id !== null && $id > 0) {
115 $tp->add_class('wp-image-' . $id);
116 $tp->add_class('size-' . ($attrs['mediaSizeSlug'] ?? 'full'));
117 }
118
119 if ($id === null || $id <= 0) {
120 $tp->remove_attribute('srcset');
121 $tp->remove_attribute('sizes');
122 }
123 $patched = true;
124 break;
125 }
126 }
127 if ($patched) {
128 $patchedChunk = $this->stripImportMarker($tp->get_updated_html(), 'div');
129 $innerContent[$i] = $patchedChunk;
130 $block['innerContent'] = $innerContent;
131 // Inspection-only; the serializer rebuilds innerHTML from innerContent.
132 $block['innerHTML'] = implode('', array_map(
133 static function ($c) {
134 return is_string($c) ? $c : '';
135 },
136 $innerContent
137 ));
138 break;
139 }
140 }
141
142 return $block;
143 }
144
145 // The Launch-import marker lives in attrs.className for media-text (it
146 // renders into the wrapper div); strip it so a Quick Edit replace claims
147 // the image the same way the agent's change-image flow does.
148 private function dropImportClass(array $attrs): array
149 {
150 if (!isset($attrs['className']) || !is_string($attrs['className'])) {
151 return $attrs;
152 }
153 $classes = array_filter(
154 preg_split('/\s+/', $attrs['className']) ?: [],
155 static function ($c) {
156 return $c !== '' && $c !== 'extendify-image-import';
157 }
158 );
159 if ($classes) {
160 $attrs['className'] = implode(' ', $classes);
161 } else {
162 unset($attrs['className']);
163 }
164 return $attrs;
165 }
166
167 // Mirror of Image::stripImportMarker — drop the marker class that survives
168 // in the rendered wrapper but not in attrs after the className edit above.
169 private function stripImportMarker(string $html, string $tagName): string
170 {
171 $tp = new \WP_HTML_Tag_Processor($html);
172 if (!$tp->next_tag($tagName)) {
173 return $html;
174 }
175 if (!$tp->has_class('extendify-image-import')) {
176 return $html;
177 }
178 $tp->remove_class('extendify-image-import');
179 return $tp->get_updated_html();
180 }
181 }
182