137 lines
5.0 KiB
PHP
137 lines
5.0 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
namespace Esun\BlogImport\Model;
|
|
|
|
use Magento\Framework\Exception\LocalizedException;
|
|
|
|
class HtmlProcessor
|
|
{
|
|
private const ESUN_MEDIA_HOSTS = ['esun3d.com', 'www.esun3d.com'];
|
|
|
|
private const MEDIA_EXTENSIONS = [
|
|
'avif', 'bmp', 'gif', 'jpeg', 'jpg', 'm4v', 'mov', 'mp4', 'ogg', 'png', 'svg', 'tif', 'tiff', 'webm', 'webp',
|
|
];
|
|
|
|
/**
|
|
* @return array{html: string, media: string[]}
|
|
* @throws LocalizedException
|
|
*/
|
|
public function process(string $htmlPath, string $mediaSubdirectory): array
|
|
{
|
|
$html = file_get_contents($htmlPath);
|
|
if ($html === false || trim($html) === '') {
|
|
throw new LocalizedException(__('HTML file is empty or unreadable: %1', $htmlPath));
|
|
}
|
|
|
|
$previous = libxml_use_internal_errors(true);
|
|
$document = new \DOMDocument('1.0', 'UTF-8');
|
|
$loaded = $document->loadHTML(
|
|
'<?xml encoding="UTF-8"><div id="esun-import-root">' . $html . '</div>',
|
|
LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD
|
|
);
|
|
libxml_clear_errors();
|
|
libxml_use_internal_errors($previous);
|
|
if (!$loaded) {
|
|
throw new LocalizedException(__('Unable to parse HTML file: %1', $htmlPath));
|
|
}
|
|
|
|
$xpath = new \DOMXPath($document);
|
|
$article = $xpath->query(
|
|
'//*[contains(concat(" ", normalize-space(@class), " "), " blog-article ")]'
|
|
)->item(0);
|
|
$contentRoot = $article instanceof \DOMElement
|
|
? $article
|
|
: $xpath->query('//*[@id="esun-import-root"]')->item(0);
|
|
if (!$contentRoot instanceof \DOMElement) {
|
|
throw new LocalizedException(__('No importable HTML content found in %1.', $htmlPath));
|
|
}
|
|
|
|
$removeQueries = [
|
|
'.//h1[contains(concat(" ", normalize-space(@class), " "), " news-title ")]',
|
|
'.//*[contains(concat(" ", normalize-space(@class), " "), " news-time ")]',
|
|
'.//script',
|
|
'.//*[contains(concat(" ", normalize-space(@class), " "), " picker ")]',
|
|
'./*[contains(concat(" ", normalize-space(@class), " "), " clear ")]',
|
|
];
|
|
foreach ($removeQueries as $query) {
|
|
$nodes = iterator_to_array($xpath->query($query, $contentRoot));
|
|
foreach ($nodes as $node) {
|
|
$node->parentNode?->removeChild($node);
|
|
}
|
|
}
|
|
|
|
$media = [];
|
|
$mediaReplacements = [];
|
|
foreach (iterator_to_array($xpath->query('.//*', $contentRoot)) as $element) {
|
|
if (!$element instanceof \DOMElement) {
|
|
continue;
|
|
}
|
|
foreach (['src', 'href', 'poster'] as $attribute) {
|
|
if (!$element->hasAttribute($attribute)) {
|
|
continue;
|
|
}
|
|
$relativeMediaPath = $this->getLegacyMediaPath($element->getAttribute($attribute));
|
|
if ($relativeMediaPath === null) {
|
|
continue;
|
|
}
|
|
$media[$relativeMediaPath] = $relativeMediaPath;
|
|
$placeholder = sprintf('__ESUN_IMPORTED_MEDIA_%d__', count($mediaReplacements));
|
|
$mediaReplacements[$placeholder] = sprintf(
|
|
"{{media url='%s/%s'}}",
|
|
$mediaSubdirectory,
|
|
str_replace("'", ''', $relativeMediaPath)
|
|
);
|
|
$element->setAttribute($attribute, $placeholder);
|
|
}
|
|
}
|
|
|
|
$output = '';
|
|
foreach ($contentRoot->childNodes as $child) {
|
|
$output .= $document->saveHTML($child);
|
|
}
|
|
$output = trim(strtr($output, $mediaReplacements));
|
|
if ($output === '') {
|
|
throw new LocalizedException(__('HTML content became empty after processing: %1', $htmlPath));
|
|
}
|
|
|
|
return ['html' => $output, 'media' => array_values($media)];
|
|
}
|
|
|
|
private function getLegacyMediaPath(string $url): ?string
|
|
{
|
|
$url = html_entity_decode(trim($url), ENT_QUOTES | ENT_HTML5);
|
|
$path = '';
|
|
if (str_starts_with($url, '/uploads/')) {
|
|
$embeddedUrl = rawurldecode(substr($url, strlen('/uploads/')));
|
|
if (preg_match('#^(?:https?:)?//#iu', $embeddedUrl)) {
|
|
return null;
|
|
}
|
|
$path = $url;
|
|
} elseif (preg_match(
|
|
'#^(?:https?:)?//([^/]+)(/.*)$#iu',
|
|
$url,
|
|
$matches
|
|
)) {
|
|
if (!in_array(strtolower($matches[1]), self::ESUN_MEDIA_HOSTS, true)) {
|
|
return null;
|
|
}
|
|
$path = $matches[2];
|
|
}
|
|
if ($path === '') {
|
|
return null;
|
|
}
|
|
$path = (string)preg_split('/[?#]/', $path, 2)[0];
|
|
$filename = rawurldecode(basename($path));
|
|
if ($filename === '' || $filename === '.' || $filename === '..') {
|
|
return null;
|
|
}
|
|
$extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
|
|
if (!in_array($extension, self::MEDIA_EXTENSIONS, true)) {
|
|
return null;
|
|
}
|
|
|
|
return $filename;
|
|
}
|
|
}
|