PluginProbe
Extendify / 3.0.4
Extendify v3.0.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.0.4, at app/Shared/Services/Import/BlocksUpdater.php

313 lines 10.3 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 foreach ($this->classesToTarget as $cls) {
109 $block['innerHTML'] = str_replace($cls, '', $block['innerHTML']);
110 $block['innerContent'] = array_map(function ($item) use ($cls) {
111 return !is_null($item) ? str_replace($cls, '', $item) : null;
112 }, ($block['innerContent'] ?? []));
113 }
114 }
115
116 return $block;
117 }
118
119 $upload = (new ImageUploader())->uploadImage($image, $author);
120
121 if (is_wp_error($upload)) {
122 // This is used for recording the error in the logs.
123 return new \WP_Error($upload->get_error_code(), $upload->get_error_message());
124 }
125
126 $block = $this->updateNewBlockAttributes($block, $upload);
127 $block = $this->addImageAttributes($block, $upload);
128 $block = $this->removeTargetedClassAttribute($block);
129 $block = $this->removeClassAttributeFromAttrs($block);
130
131 return $block;
132 }
133
134 /**
135 * Return the image source link or an empty string.
136 *
137 * @param string $htmlContent The html tag that contains the image tag.
138 * @return string
139 */
140 protected function getImageSource($htmlContent)
141 {
142 $html = new \WP_HTML_Tag_Processor($htmlContent);
143 $html->next_tag('img');
144 $src = $html->get_attribute('src');
145
146 return $src && preg_match(
147 '(' . implode('|', array_map('preg_quote', ImageUploader::$imagesDomains, ['/'])) . ')i',
148 $src
149 )
150 ? $src
151 : '';
152 }
153
154 /**
155 * Update the content of the block to remove the targeted class attribute.
156 *
157 * @param array $block The block we need to update.
158 * @return array The parsed block after updates.
159 */
160 protected function removeTargetedClassAttribute(array $block)
161 {
162 $block['innerContent'] = array_map(function ($item) {
163 return !is_null($item) ? $this->removeClassAttributeFromContent($item) : null;
164 }, ($block['innerContent'] ?? []));
165
166 $block['innerHTML'] = $this->removeClassAttributeFromContent($block['innerHTML']);
167
168 return $block;
169 }
170
171 /**
172 * Remove the targeted class from the html content.
173 *
174 * @param string $content The html tag that contains the targeted class.
175 * @return string
176 */
177 protected function removeClassAttributeFromContent($content)
178 {
179 foreach ($this->classesToTarget as $targetedClass) {
180 $html = new \WP_HTML_Tag_Processor($content);
181 do {
182 $html->remove_class($targetedClass);
183 } while ($html->next_tag(['class' => $targetedClass]));
184 $content = $html->get_updated_html();
185 }
186
187 return $content;
188 }
189
190 /**
191 * Remove the targeted class from the className attrs.
192 *
193 * @param array $block The block.
194 * @return array The parsed block after updates.
195 */
196 protected function removeClassAttributeFromAttrs($block)
197 {
198 if (isset($block['attrs']['className'])) {
199 $className = is_array($block['attrs']['className'])
200 ? $block['attrs']['className']
201 : explode(' ', $block['attrs']['className']);
202 $className = array_diff($className, $this->classesToTarget);
203 $block['attrs']['className'] = implode(' ', $className);
204 }
205
206 return $block;
207 }
208
209 /**
210 * Update the block attributes with information about the image.
211 *
212 * @param array $block Block.
213 * @param array $upload The uploaded file information.
214 * @return array The parse block after updates.
215 */
216 protected function updateNewBlockAttributes(array $block, array $upload)
217 {
218 $block['attrs']['id'] = $upload['attachment_id'];
219
220 if ($block['blockName'] === 'core/media-text') {
221 $block['attrs']['mediaId'] = $upload['attachment_id'];
222 $block['attrs']['mediaLink'] = $upload['url'];
223 }
224
225 if ($block['blockName'] === 'core/cover') {
226 $block['attrs']['url'] = $upload['url'];
227 }
228
229 return $block;
230 }
231
232 /**
233 * Update the inner content for the block.
234 *
235 * @param array $block Block inner content.
236 * @param array $upload The uploaded file information.
237 * @return array
238 */
239 protected function addImageAttributes($block, $upload)
240 {
241 $isMediaText = $block['blockName'] === 'core/media-text';
242
243 $block['innerContent'] = array_map(function ($item) use ($upload, $isMediaText) {
244 return !is_null($item) ? $this->updateImageTagAttributes($item, $upload, $isMediaText) : null;
245 }, ($block['innerContent'] ?? []));
246
247 $block['innerHTML'] = $this->updateImageTagAttributes($block['innerHTML'], $upload, $isMediaText);
248
249 return $block;
250 }
251
252 /**
253 * Checks the html and content for the class name.
254 *
255 * @param array $block The block.
256 * @return boolean
257 */
258 protected function hasTargetedClassName(array $block)
259 {
260 if (
261 array_reduce($this->classesToTarget, function (bool $carry, string $targetClass) use ($block) {
262 return $carry || (strpos(($block['innerHTML'] ?? ''), $targetClass) !== false);
263 }, false)
264 ) {
265 return true;
266 }
267
268 $classList = is_array(($block['attrs']['className'] ?? null))
269 ? $block['attrs']['className']
270 : explode(' ', ($block['attrs']['className'] ?? ''));
271
272 return !empty(array_intersect($this->classesToTarget, $classList));
273 }
274
275 /**
276 * Return the new html content after making the required changes.
277 *
278 * @param string $htmlContent The html tag that contains the image tag.
279 * @param array $upload The uploaded file information.
280 * @param bool $isMediaText Is the block a media text block, if so, we need to update the style attribute.
281 * @return string
282 */
283 protected function updateImageTagAttributes($htmlContent, $upload, $isMediaText = false)
284 {
285 $html = new \WP_HTML_Tag_Processor($htmlContent);
286
287 // Media text block needs a bit more work to update the style attribute.
288 if (
289 $isMediaText && $html->next_tag([
290 'tag_name' => 'figure',
291 'class_name' => 'wp-block-media-text__media',
292 ])
293 ) {
294 $style = $html->get_attribute('style');
295 if (preg_match('/:url\(*.+\)/m', $style, $matches)) {
296 $result = str_replace($matches[0], ':url(' . $upload['url'] . ')', $style);
297 $html->set_attribute('style', ($result ?? ''));
298 }
299 }
300
301 // Update the image tag attributes.
302 if ($html->next_tag('img')) {
303 $html->set_attribute('src', $upload['url']);
304 $html->add_class('wp-image-' . $upload['attachment_id']);
305 if ($isMediaText && !$html->has_class('size-full')) {
306 $html->add_class('size-full');
307 }
308 }
309
310 return $html->get_updated_html();
311 }
312 }
313