init
This commit is contained in:
@@ -0,0 +1,242 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Esun\BlogImport\Model;
|
||||
|
||||
use Magento\Framework\Exception\LocalizedException;
|
||||
use Magento\Framework\Serialize\Serializer\Json;
|
||||
|
||||
class SourceReader
|
||||
{
|
||||
private const SOURCE_FILES = [
|
||||
'industry' => 'esun_industry_details.json',
|
||||
'activities' => 'esun_activities_details.json',
|
||||
'exhibition' => 'esun_exhibition_details.json',
|
||||
'news' => 'esun_news_details.json',
|
||||
];
|
||||
|
||||
private const LIST_FILES = [
|
||||
'industry' => 'esun_industry_all.json',
|
||||
'activities' => 'esun_activities_all.json',
|
||||
'exhibition' => 'esun_exhibition_all.json',
|
||||
'news' => 'esun_news_all.json',
|
||||
];
|
||||
|
||||
public function __construct(private readonly Json $json)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{records: array<string, array<string, mixed>>, json_records: int, duplicate_records: int}
|
||||
* @throws LocalizedException
|
||||
*/
|
||||
public function read(string $sourceDirectory): array
|
||||
{
|
||||
$sourceRoot = realpath($sourceDirectory);
|
||||
if ($sourceRoot === false || !is_dir($sourceRoot) || !is_readable($sourceRoot)) {
|
||||
throw new LocalizedException(__('Import source is not a readable directory: %1', $sourceDirectory));
|
||||
}
|
||||
|
||||
$records = [];
|
||||
$jsonRecords = 0;
|
||||
foreach (self::SOURCE_FILES as $category => $filename) {
|
||||
$listItems = $this->readListItems($sourceRoot, $category);
|
||||
$jsonPath = $sourceRoot . DIRECTORY_SEPARATOR . $filename;
|
||||
if (!is_file($jsonPath) || !is_readable($jsonPath)) {
|
||||
throw new LocalizedException(__('Required JSON file is missing or unreadable: %1', $jsonPath));
|
||||
}
|
||||
|
||||
try {
|
||||
$data = $this->json->unserialize((string)file_get_contents($jsonPath));
|
||||
} catch (\InvalidArgumentException $exception) {
|
||||
throw new LocalizedException(__('Invalid JSON in %1: %2', $filename, $exception->getMessage()));
|
||||
}
|
||||
if (!is_array($data) || !isset($data['details']) || !is_array($data['details'])) {
|
||||
throw new LocalizedException(__('The details array is missing from %1.', $filename));
|
||||
}
|
||||
if (isset($data['total_count']) && (int)$data['total_count'] !== count($data['details'])) {
|
||||
throw new LocalizedException(
|
||||
__('The total_count in %1 does not match its details count.', $filename)
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($data['details'] as $index => $item) {
|
||||
$jsonRecords++;
|
||||
if (!is_array($item)) {
|
||||
throw new LocalizedException(__('Invalid item %1 in %2.', $index + 1, $filename));
|
||||
}
|
||||
$this->validateItem($item, $index, $filename);
|
||||
|
||||
$sourceUrl = trim((string)$item['sourceUrl']);
|
||||
if (!isset($listItems[$sourceUrl])) {
|
||||
throw new LocalizedException(
|
||||
__('No matching list item was found for %1 in %2.', $sourceUrl, self::LIST_FILES[$category])
|
||||
);
|
||||
}
|
||||
$listItem = $listItems[$sourceUrl];
|
||||
$path = (string)parse_url($sourceUrl, PHP_URL_PATH);
|
||||
$pathSegments = array_values(array_filter(explode('/', trim($path, '/')), 'strlen'));
|
||||
$sourceUrlKey = $pathSegments ? (string)end($pathSegments) : '';
|
||||
if ($sourceUrlKey === '') {
|
||||
throw new LocalizedException(
|
||||
__('Cannot derive a URL key from item %1 in %2.', $index + 1, $filename)
|
||||
);
|
||||
}
|
||||
|
||||
$htmlPath = $this->resolveHtmlPath($sourceRoot, (string)$item['localHtmlFile']);
|
||||
if (isset($records[$sourceUrl])) {
|
||||
$existing = $records[$sourceUrl];
|
||||
if ($existing['title'] !== trim((string)$item['title'])
|
||||
|| $existing['publish_date'] !== (string)$item['publishDate']
|
||||
) {
|
||||
throw new LocalizedException(
|
||||
__('Conflicting duplicate post data for source URL %1.', $sourceUrl)
|
||||
);
|
||||
}
|
||||
if ($records[$sourceUrl]['short_description'] !== ''
|
||||
&& $listItem['summary'] !== ''
|
||||
&& $records[$sourceUrl]['short_description'] !== $listItem['summary']
|
||||
) {
|
||||
throw new LocalizedException(__('Conflicting summaries for source URL %1.', $sourceUrl));
|
||||
}
|
||||
if ($records[$sourceUrl]['image_filename'] !== ''
|
||||
&& $listItem['image_filename'] !== ''
|
||||
&& $records[$sourceUrl]['image_filename'] !== $listItem['image_filename']
|
||||
) {
|
||||
throw new LocalizedException(__('Conflicting list images for source URL %1.', $sourceUrl));
|
||||
}
|
||||
if ($records[$sourceUrl]['short_description'] === '') {
|
||||
$records[$sourceUrl]['short_description'] = $listItem['summary'];
|
||||
}
|
||||
if ($records[$sourceUrl]['image_filename'] === '') {
|
||||
$records[$sourceUrl]['image_filename'] = $listItem['image_filename'];
|
||||
}
|
||||
$records[$sourceUrl]['categories'][$category] = $category;
|
||||
continue;
|
||||
}
|
||||
|
||||
$records[$sourceUrl] = [
|
||||
'title' => trim((string)$item['title']),
|
||||
'source_url' => $sourceUrl,
|
||||
'source_url_key' => $sourceUrlKey,
|
||||
'publish_date' => (string)$item['publishDate'],
|
||||
'html_path' => $htmlPath,
|
||||
'short_description' => $listItem['summary'],
|
||||
'image_filename' => $listItem['image_filename'],
|
||||
'categories' => [$category => $category],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'records' => $records,
|
||||
'json_records' => $jsonRecords,
|
||||
'duplicate_records' => $jsonRecords - count($records),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, array{summary: string, image_filename: string}>
|
||||
* @throws LocalizedException
|
||||
*/
|
||||
private function readListItems(string $sourceRoot, string $category): array
|
||||
{
|
||||
$filename = self::LIST_FILES[$category];
|
||||
$jsonPath = $sourceRoot . DIRECTORY_SEPARATOR . $filename;
|
||||
if (!is_file($jsonPath) || !is_readable($jsonPath)) {
|
||||
throw new LocalizedException(__('Required list JSON file is missing or unreadable: %1', $jsonPath));
|
||||
}
|
||||
|
||||
try {
|
||||
$data = $this->json->unserialize((string)file_get_contents($jsonPath));
|
||||
} catch (\InvalidArgumentException $exception) {
|
||||
throw new LocalizedException(__('Invalid JSON in %1: %2', $filename, $exception->getMessage()));
|
||||
}
|
||||
if (!is_array($data) || !isset($data['news']) || !is_array($data['news'])) {
|
||||
throw new LocalizedException(__('The news array is missing from %1.', $filename));
|
||||
}
|
||||
if (isset($data['total_count']) && (int)$data['total_count'] !== count($data['news'])) {
|
||||
throw new LocalizedException(__('The total_count in %1 does not match its news count.', $filename));
|
||||
}
|
||||
|
||||
$result = [];
|
||||
foreach ($data['news'] as $index => $item) {
|
||||
if (!is_array($item) || !isset($item['link']) || !is_string($item['link']) || trim($item['link']) === '') {
|
||||
throw new LocalizedException(__('Missing or invalid link in item %1 of %2.', $index + 1, $filename));
|
||||
}
|
||||
$link = trim($item['link']);
|
||||
if (isset($result[$link])) {
|
||||
throw new LocalizedException(__('Duplicate link %1 in %2.', $link, $filename));
|
||||
}
|
||||
$summary = isset($item['summary']) && is_string($item['summary']) ? trim($item['summary']) : '';
|
||||
$imageUrl = isset($item['imageUrl']) && is_string($item['imageUrl']) ? trim($item['imageUrl']) : '';
|
||||
$result[$link] = [
|
||||
'summary' => $summary,
|
||||
'image_filename' => $this->extractImageFilename($imageUrl, $index, $filename),
|
||||
];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/** @throws LocalizedException */
|
||||
private function extractImageFilename(string $imageUrl, int $index, string $filename): string
|
||||
{
|
||||
if ($imageUrl === '') {
|
||||
return '';
|
||||
}
|
||||
$path = (string)preg_split('/[?#]/', $imageUrl, 2)[0];
|
||||
$imageFilename = rawurldecode(basename($path));
|
||||
if ($imageFilename === '' || $imageFilename === '.' || $imageFilename === '..') {
|
||||
throw new LocalizedException(__('Invalid imageUrl in item %1 of %2.', $index + 1, $filename));
|
||||
}
|
||||
|
||||
return $imageFilename;
|
||||
}
|
||||
|
||||
/** @throws LocalizedException */
|
||||
private function validateItem(array $item, int $index, string $filename): void
|
||||
{
|
||||
foreach (['title', 'sourceUrl', 'publishDate', 'localHtmlFile'] as $field) {
|
||||
if (!isset($item[$field]) || !is_string($item[$field]) || trim($item[$field]) === '') {
|
||||
throw new LocalizedException(
|
||||
__('Missing or invalid %1 in item %2 of %3.', $field, $index + 1, $filename)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$sourceUrl = (string)$item['sourceUrl'];
|
||||
$parts = parse_url($sourceUrl);
|
||||
if (!is_array($parts)
|
||||
|| ($parts['scheme'] ?? '') !== 'https'
|
||||
|| strtolower((string)($parts['host'] ?? '')) !== 'www.esun3d.com'
|
||||
|| !str_starts_with((string)($parts['path'] ?? ''), '/news/')
|
||||
) {
|
||||
throw new LocalizedException(__('Invalid sourceUrl in item %1 of %2.', $index + 1, $filename));
|
||||
}
|
||||
|
||||
$date = \DateTimeImmutable::createFromFormat('!Y-m-d', (string)$item['publishDate']);
|
||||
if ($date === false || $date->format('Y-m-d') !== $item['publishDate']) {
|
||||
throw new LocalizedException(__('Invalid publishDate in item %1 of %2.', $index + 1, $filename));
|
||||
}
|
||||
}
|
||||
|
||||
/** @throws LocalizedException */
|
||||
private function resolveHtmlPath(string $sourceRoot, string $relativePath): string
|
||||
{
|
||||
if ($relativePath === '' || str_contains($relativePath, "\0") || str_starts_with($relativePath, '/')) {
|
||||
throw new LocalizedException(__('Invalid localHtmlFile path: %1', $relativePath));
|
||||
}
|
||||
$htmlPath = realpath($sourceRoot . DIRECTORY_SEPARATOR . $relativePath);
|
||||
$rootPrefix = rtrim($sourceRoot, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
|
||||
if ($htmlPath === false
|
||||
|| !is_file($htmlPath)
|
||||
|| !is_readable($htmlPath)
|
||||
|| !str_starts_with($htmlPath, $rootPrefix)
|
||||
) {
|
||||
throw new LocalizedException(__('HTML file is missing, unreadable, or outside the source: %1', $relativePath));
|
||||
}
|
||||
|
||||
return $htmlPath;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user