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 / Shared / Services / Import / BlocksUpdater.php

BlocksUpdater.php in Extendify 3.1.4, at app/Shared/Services/Import/BlocksUpdater.php

337 lines 11.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Blocks uploader class
5 */
6
7 namespace Extendify\Shared\Services\Import;
8
9 defined('ABSPATH') || die('No direct access.');
10
11 /**
12 * This class responsible for updating the blocks.
13 */
14
15 class BlocksUpdater
16 {
17 /**
18 * The class to target.
19 *
20 * @var array The class names that we want to target.
21 */
22 protected $classesToTarget = ['extendify-image-import', 'ext-import'];
23
24 /**
25 * Update the content of the blocks in a specific post.
26 *
27 * @param \WP_Post $post WordPress post.
28 * @return string The updated post content.
29 */
30 public function getModifiedBlocksInPost($post)
31 {
32 $blocks = parse_blocks($post->post_content);
33
34 $updatedBlocks = $this->processAndMutateBlocks($blocks, $post->post_author);
35
36 return str_replace('\u002d\u002d', '--', serialize_blocks($updatedBlocks));
37 }
38
39 /**
40 * The logic for the update blocks code.
41 *
42 * @param array $blocks WordPress post blocks.
43 * @param string $author WordPress post author.
44 * @return array
45 */
46 protected function processAndMutateBlocks($blocks, $author)
47 {
48 return array_map(function ($block) use ($author) {
49 $block = $this->processBlock($block, $author);
50
51 if (is_wp_error($block)) {
52 return $block;
53 }
54
55 if (!empty($block['innerBlocks']) && !is_null($block['blockName'])) {
56 $block['innerBlocks'] = $this->processAndMutateBlocks($block['innerBlocks'], $author);
57 }
58
59 return $block;
60 }, $blocks);
61 }
62
63 /**
64 * Check if the block should not be processed.
65 *
66 * @param array $block the core/image block that we need to update.
67 * @return bool
68 */
69 protected function needsImageProcessing($block)
70 {
71 // Check if the attributes has an element called `className` with the value of `extendify-image-import`.
72 // if the returned array is empty, then the block should not be processed.
73 $attrs = ($block['attrs'] ?? []);
74 if (array_key_exists('className', $attrs)) {
75 $className = is_array($attrs['className']) ? $attrs['className'] : explode(' ', $attrs['className']);
76 return !empty(array_intersect($this->classesToTarget, $className));
77 }
78
79 return false;
80 }
81
82 /**
83 * This function process the image block and return the new code.
84 *
85 * @param array $block the core/image block that we need to update.
86 * @param string $author the post author.
87 * @return array|\WP_Error
88 */
89 protected function processBlock($block, $author)
90 {
91 // Check if the block has the targeted class anywhere (even in unexpected places).
92 $needsToRemoveClassName = $this->hasTargetedClassName($block);
93
94 // Return the block unmodified if we don't find the class.
95 if (!$this->needsImageProcessing($block) && !$needsToRemoveClassName) {
96 return $block;
97 }
98
99 // Check if the block has an image.
100 $image = $this->getImageSource($block['innerHTML']);
101
102 if (!$image) {
103 // If we found the class, but no image, then just remove the class.
104 if ($needsToRemoveClassName) {
105 $block = $this->removeTargetedClassAttribute($block);
106 $block = $this->removeClassAttributeFromAttrs($block);
107 // In some cases the block might become unformatted.
108 $block['innerHTML'] = $this->stripClassTokens($block['innerHTML']);
109 $block['innerContent'] = array_map(function ($item) {
110 return !is_null($item) ? $this->stripClassTokens($item) : null;
111 }, ($block['innerContent'] ?? []));
112 }
113
114 return $block;
115 }
116
117 $upload = (new ImageUploader())->uploadImage($image, $author);
118
119 if (is_wp_error($upload)) {
120 // This is used for recording the error in the logs.
121 return new \WP_Error($upload->get_error_code(), $upload->get_error_message());
122 }
123
124 $block = $this->updateNewBlockAttributes($block, $upload);
125 $block = $this->addImageAttributes($block, $upload);
126 $block = $this->removeTargetedClassAttribute($block);
127 $block = $this->removeClassAttributeFromAttrs($block);
128
129 return $block;
130 }
131
132 /**
133 * Return the image source link or an empty string.
134 *
135 * @param string $htmlContent The html tag that contains the image tag.
136 * @return string
137 */
138 protected function getImageSource($htmlContent)
139 {
140 $html = new \WP_HTML_Tag_Processor($htmlContent);
141 $html->next_tag('img');
142 $src = $html->get_attribute('src');
143
144 return $src && preg_match(
145 '(' . implode('|', array_map('preg_quote', ImageUploader::$imagesDomains, ['/'])) . ')i',
146 $src
147 )
148 ? $src
149 : '';
150 }
151
152 /**
153 * Update the content of the block to remove the targeted class attribute.
154 *
155 * @param array $block The block we need to update.
156 * @return array The parsed block after updates.
157 */
158 protected function removeTargetedClassAttribute(array $block)
159 {
160 $block['innerContent'] = array_map(function ($item) {
161 return !is_null($item) ? $this->removeClassAttributeFromContent($item) : null;
162 }, ($block['innerContent'] ?? []));
163
164 $block['innerHTML'] = $this->removeClassAttributeFromContent($block['innerHTML']);
165
166 return $block;
167 }
168
169 /**
170 * Remove the targeted class from the html content.
171 *
172 * @param string $content The html tag that contains the targeted class.
173 * @return string
174 */
175 protected function removeClassAttributeFromContent($content)
176 {
177 foreach ($this->classesToTarget as $targetedClass) {
178 $html = new \WP_HTML_Tag_Processor($content);
179 do {
180 $html->remove_class($targetedClass);
181 } while ($html->next_tag(['class' => $targetedClass]));
182 $content = $html->get_updated_html();
183 }
184
185 return $content;
186 }
187
188 /**
189 * Build a pattern matching the class only where it stands as a whole token.
190 *
191 * @param string $targetedClass The class name to match.
192 * @return string
193 */
194 protected function classTokenPattern($targetedClass)
195 {
196 // Uploaded filenames embed the class name (ext-imported-*.jpg); a bare substring match eats the src.
197 return '/(?<=[\s"\'])' . preg_quote($targetedClass, '/') . '(?=[\s"\'])/';
198 }
199
200 /**
201 * Remove the targeted classes from html the tag processor left untouched.
202 *
203 * @param string $content The html content.
204 * @return string
205 */
206 protected function stripClassTokens($content)
207 {
208 foreach ($this->classesToTarget as $targetedClass) {
209 $content = preg_replace($this->classTokenPattern($targetedClass), '', $content);
210 }
211
212 return $content;
213 }
214
215 /**
216 * Remove the targeted class from the className attrs.
217 *
218 * @param array $block The block.
219 * @return array The parsed block after updates.
220 */
221 protected function removeClassAttributeFromAttrs($block)
222 {
223 if (isset($block['attrs']['className'])) {
224 $className = is_array($block['attrs']['className'])
225 ? $block['attrs']['className']
226 : explode(' ', $block['attrs']['className']);
227 $className = array_diff($className, $this->classesToTarget);
228 $block['attrs']['className'] = implode(' ', $className);
229 }
230
231 return $block;
232 }
233
234 /**
235 * Update the block attributes with information about the image.
236 *
237 * @param array $block Block.
238 * @param array $upload The uploaded file information.
239 * @return array The parse block after updates.
240 */
241 protected function updateNewBlockAttributes(array $block, array $upload)
242 {
243 $block['attrs']['id'] = $upload['attachment_id'];
244
245 if ($block['blockName'] === 'core/media-text') {
246 $block['attrs']['mediaId'] = $upload['attachment_id'];
247 $block['attrs']['mediaLink'] = $upload['url'];
248 }
249
250 if ($block['blockName'] === 'core/cover') {
251 $block['attrs']['url'] = $upload['url'];
252 }
253
254 return $block;
255 }
256
257 /**
258 * Update the inner content for the block.
259 *
260 * @param array $block Block inner content.
261 * @param array $upload The uploaded file information.
262 * @return array
263 */
264 protected function addImageAttributes($block, $upload)
265 {
266 $isMediaText = $block['blockName'] === 'core/media-text';
267
268 $block['innerContent'] = array_map(function ($item) use ($upload, $isMediaText) {
269 return !is_null($item) ? $this->updateImageTagAttributes($item, $upload, $isMediaText) : null;
270 }, ($block['innerContent'] ?? []));
271
272 $block['innerHTML'] = $this->updateImageTagAttributes($block['innerHTML'], $upload, $isMediaText);
273
274 return $block;
275 }
276
277 /**
278 * Checks the html and content for the class name.
279 *
280 * @param array $block The block.
281 * @return boolean
282 */
283 protected function hasTargetedClassName(array $block)
284 {
285 $innerHTML = ($block['innerHTML'] ?? '');
286 foreach ($this->classesToTarget as $targetedClass) {
287 if (preg_match($this->classTokenPattern($targetedClass), $innerHTML)) {
288 return true;
289 }
290 }
291
292 $classList = is_array(($block['attrs']['className'] ?? null))
293 ? $block['attrs']['className']
294 : explode(' ', ($block['attrs']['className'] ?? ''));
295
296 return !empty(array_intersect($this->classesToTarget, $classList));
297 }
298
299 /**
300 * Return the new html content after making the required changes.
301 *
302 * @param string $htmlContent The html tag that contains the image tag.
303 * @param array $upload The uploaded file information.
304 * @param bool $isMediaText Is the block a media text block, if so, we need to update the style attribute.
305 * @return string
306 */
307 protected function updateImageTagAttributes($htmlContent, $upload, $isMediaText = false)
308 {
309 $html = new \WP_HTML_Tag_Processor($htmlContent);
310
311 // Media text block needs a bit more work to update the style attribute.
312 if (
313 $isMediaText && $html->next_tag([
314 'tag_name' => 'figure',
315 'class_name' => 'wp-block-media-text__media',
316 ])
317 ) {
318 $style = $html->get_attribute('style');
319 if (preg_match('/:url\(*.+\)/m', $style, $matches)) {
320 $result = str_replace($matches[0], ':url(' . $upload['url'] . ')', $style);
321 $html->set_attribute('style', ($result ?? ''));
322 }
323 }
324
325 // Update the image tag attributes.
326 if ($html->next_tag('img')) {
327 $html->set_attribute('src', $upload['url']);
328 $html->add_class('wp-image-' . $upload['attachment_id']);
329 if ($isMediaText && !$html->has_class('size-full')) {
330 $html->add_class('size-full');
331 }
332 }
333
334 return $html->get_updated_html();
335 }
336 }
337