post_content); // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps $updatedBlocks = $this->processAndMutateBlocks($blocks, $post->post_author); return str_replace('\u002d\u002d', '--', serialize_blocks($updatedBlocks)); } /** * The logic for the update blocks code. * * @param array $blocks WordPress post blocks. * @param string $author WordPress post author. * @return array */ protected function processAndMutateBlocks($blocks, $author) { return array_map(function ($block) use ($author) { $block = $this->processBlock($block, $author); if (is_wp_error($block)) { return $block; } if (!empty($block['innerBlocks']) && !is_null($block['blockName'])) { $block['innerBlocks'] = $this->processAndMutateBlocks($block['innerBlocks'], $author); } return $block; }, $blocks); } /** * Check if the block should not be processed. * * @param array $block the core/image block that we need to update. * @return bool */ protected function needsImageProcessing($block) { // Check if the attributes has an element called `className` with the value of `extendify-image-import`. // if the returned array is empty, then the block should not be processed. $attrs = ($block['attrs'] ?? []); if (array_key_exists('className', $attrs)) { $className = is_array($attrs['className']) ? $attrs['className'] : explode(' ', $attrs['className']); return !empty(array_intersect($this->classesToTarget, $className)); } return false; } /** * This function process the image block and return the new code. * * @param array $block the core/image block that we need to update. * @param string $author the post author. * @return array|\WP_Error */ protected function processBlock($block, $author) { // Check if the block has the targeted class anywhere (even in unexpected places). $needsToRemoveClassName = $this->hasTargetedClassName($block); // Return the block unmodified if we don't find the class. if (!$this->needsImageProcessing($block) && !$needsToRemoveClassName) { return $block; } // Check if the block has an image. $image = $this->getImageSource($block['innerHTML']); if (!$image) { // If we found the class, but no image, then just remove the class. if ($needsToRemoveClassName) { $block = $this->removeTargetedClassAttribute($block); $block = $this->removeClassAttributeFromAttrs($block); // In some cases the block might become unformatted. foreach ($this->classesToTarget as $cls) { $block['innerHTML'] = str_replace($cls, '', $block['innerHTML']); $block['innerContent'] = array_map(function ($item) use ($cls) { return !is_null($item) ? str_replace($cls, '', $item) : null; }, ($block['innerContent'] ?? [])); } } return $block; } $upload = (new ImageUploader())->uploadImage($image, $author); if (is_wp_error($upload)) { // This is used for recording the error in the logs. return new \WP_Error($upload->get_error_code(), $upload->get_error_message()); } $block = $this->updateNewBlockAttributes($block, $upload); $block = $this->addImageAttributes($block, $upload); $block = $this->removeTargetedClassAttribute($block); $block = $this->removeClassAttributeFromAttrs($block); return $block; } /** * Return the image source link or an empty string. * * @param string $htmlContent The html tag that contains the image tag. * @return string */ protected function getImageSource($htmlContent) { $html = new \WP_HTML_Tag_Processor($htmlContent); $html->next_tag('img'); $src = $html->get_attribute('src'); return $src && preg_match('(' . implode('|', array_map('preg_quote', ImageUploader::$imagesDomains, ['/'])) . ')i', $src) ? $src : ''; } /** * Update the content of the block to remove the targeted class attribute. * * @param array $block The block we need to update. * @return array The parsed block after updates. */ protected function removeTargetedClassAttribute(array $block) { $block['innerContent'] = array_map(function ($item) { return !is_null($item) ? $this->removeClassAttributeFromContent($item) : null; }, ($block['innerContent'] ?? [])); $block['innerHTML'] = $this->removeClassAttributeFromContent($block['innerHTML']); return $block; } /** * Remove the targeted class from the html content. * * @param string $content The html tag that contains the targeted class. * @return string */ protected function removeClassAttributeFromContent($content) { foreach ($this->classesToTarget as $targetedClass) { $html = new \WP_HTML_Tag_Processor($content); do { $html->remove_class($targetedClass); } while ($html->next_tag(['class' => $targetedClass])); $content = $html->get_updated_html(); } return $content; } /** * Remove the targeted class from the className attrs. * * @param array $block The block. * @return array The parsed block after updates. */ protected function removeClassAttributeFromAttrs($block) { if (isset($block['attrs']['className'])) { $className = is_array($block['attrs']['className']) ? $block['attrs']['className'] : explode(' ', $block['attrs']['className']); $className = array_diff($className, $this->classesToTarget); $block['attrs']['className'] = implode(' ', $className); } return $block; } /** * Update the block attributes with information about the image. * * @param array $block Block. * @param array $upload The uploaded file information. * @return array The parse block after updates. */ protected function updateNewBlockAttributes(array $block, array $upload) { $block['attrs']['id'] = $upload['attachment_id']; if ($block['blockName'] === 'core/media-text') { $block['attrs']['mediaId'] = $upload['attachment_id']; $block['attrs']['mediaLink'] = $upload['url']; } if ($block['blockName'] === 'core/cover') { $block['attrs']['url'] = $upload['url']; } return $block; } /** * Update the inner content for the block. * * @param array $block Block inner content. * @param array $upload The uploaded file information. * @return array */ protected function addImageAttributes($block, $upload) { $isMediaText = $block['blockName'] === 'core/media-text'; $block['innerContent'] = array_map(function ($item) use ($upload, $isMediaText) { return !is_null($item) ? $this->updateImageTagAttributes($item, $upload, $isMediaText) : null; }, ($block['innerContent'] ?? [])); $block['innerHTML'] = $this->updateImageTagAttributes($block['innerHTML'], $upload, $isMediaText); return $block; } /** * Checks the html and content for the class name. * * @param array $block The block. * @return boolean */ protected function hasTargetedClassName(array $block) { if (array_reduce($this->classesToTarget, function (bool $carry, string $targetClass) use ($block) { return $carry || (strpos(($block['innerHTML'] ?? ''), $targetClass) !== false); }, false) ) { return true; } $classList = is_array(($block['attrs']['className'] ?? null)) ? $block['attrs']['className'] : explode(' ', ($block['attrs']['className'] ?? '')); return !empty(array_intersect($this->classesToTarget, $classList)); } /** * Return the new html content after making the required changes. * * @param string $htmlContent The html tag that contains the image tag. * @param array $upload The uploaded file information. * @param bool $isMediaText Is the block a media text block, if so, we need to update the style attribute. * @return string */ protected function updateImageTagAttributes($htmlContent, $upload, $isMediaText = false) { $html = new \WP_HTML_Tag_Processor($htmlContent); // Media text block needs a bit more work to update the style attribute. if ($isMediaText && $html->next_tag([ 'tag_name' => 'figure', 'class_name' => 'wp-block-media-text__media', ]) ) { $style = $html->get_attribute('style'); if (preg_match('/:url\(*.+\)/m', $style, $matches)) { $result = str_replace($matches[0], ':url(' . $upload['url'] . ')', $style); $html->set_attribute('style', ($result ?? '')); } } // Update the image tag attributes. if ($html->next_tag('img')) { $html->set_attribute('src', $upload['url']); $html->add_class('wp-image-' . $upload['attachment_id']); if ($isMediaText && !$html->has_class('size-full')) { $html->add_class('size-full'); } } return $html->get_updated_html(); } }