185 lines
7.3 KiB
PHP
185 lines
7.3 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
namespace Esun\Catalog\Model;
|
|
|
|
use Esun\Catalog\Model\ResourceModel\SpecificationFile\CollectionFactory as FileCollectionFactory;
|
|
use Esun\Catalog\Model\ResourceModel\SpecificationImport as SpecificationImportResource;
|
|
use Esun\Catalog\Model\ResourceModel\SpecificationImport\CollectionFactory as ImportCollectionFactory;
|
|
use Magento\Framework\App\Filesystem\DirectoryList;
|
|
use Magento\Framework\Exception\LocalizedException;
|
|
use Magento\Framework\Filesystem;
|
|
|
|
class SpecificationArchiveRebuilder
|
|
{
|
|
public function __construct(
|
|
private readonly FileCollectionFactory $fileCollectionFactory,
|
|
private readonly ImportCollectionFactory $importCollectionFactory,
|
|
private readonly SpecificationImportFactory $importFactory,
|
|
private readonly SpecificationImportResource $importResource,
|
|
private readonly SpecificationMedia $media,
|
|
private readonly Filesystem $filesystem
|
|
) {
|
|
}
|
|
|
|
/**
|
|
* @return array{status:string,path:string,file_count:int,missing_count:int}
|
|
*/
|
|
public function rebuild(SpecificationBrand $brand, bool $force = false): array
|
|
{
|
|
if (!class_exists(\ZipArchive::class)) {
|
|
throw new LocalizedException(__('Archive rebuilding requires the PHP ZipArchive extension.'));
|
|
}
|
|
|
|
$brandId = (int)$brand->getId();
|
|
if ($brandId <= 0) {
|
|
throw new LocalizedException(__('The specification brand must be saved before rebuilding its archive.'));
|
|
}
|
|
|
|
$import = $this->getLatestImport($brandId);
|
|
$currentPath = $import ? (string)$import->getData('archive_file') : '';
|
|
if (!$force && $currentPath !== '' && $this->media->exists($currentPath)) {
|
|
return [
|
|
'status' => 'skipped',
|
|
'path' => $currentPath,
|
|
'file_count' => (int)$import->getData('file_count'),
|
|
'missing_count' => 0,
|
|
];
|
|
}
|
|
|
|
$brandDirectoryName = $this->sanitizeArchiveSegment((string)$brand->getData('brand_name'), 'brand-' . $brandId);
|
|
$archiveDirectory = SpecificationMedia::ARCHIVE_PATH . '/' . $brandId;
|
|
$archivePath = $archiveDirectory . '/' . $brandDirectoryName . '.zip';
|
|
|
|
if (!$force && $this->media->exists($archivePath)) {
|
|
$fileCount = (int)$this->fileCollectionFactory->create()
|
|
->addFieldToFilter('brand_id', $brandId)
|
|
->getSize();
|
|
$this->saveImport($import, $brandId, $archivePath, $fileCount);
|
|
|
|
return [
|
|
'status' => 'linked',
|
|
'path' => $archivePath,
|
|
'file_count' => $fileCount,
|
|
'missing_count' => 0,
|
|
];
|
|
}
|
|
|
|
$mediaDirectory = $this->filesystem->getDirectoryWrite(DirectoryList::MEDIA);
|
|
$mediaDirectory->create($archiveDirectory);
|
|
$temporaryPath = $archiveDirectory . '/.rebuild-' . bin2hex(random_bytes(8)) . '.zip';
|
|
$zip = new \ZipArchive();
|
|
if ($zip->open($mediaDirectory->getAbsolutePath($temporaryPath), \ZipArchive::CREATE | \ZipArchive::OVERWRITE) !== true) {
|
|
throw new LocalizedException(__('Unable to create the specification archive for brand "%1".', $brand->getData('brand_name')));
|
|
}
|
|
|
|
$fileCount = 0;
|
|
$missingCount = 0;
|
|
$entryNames = [];
|
|
try {
|
|
try {
|
|
$collection = $this->fileCollectionFactory->create()
|
|
->addFieldToFilter('brand_id', $brandId)
|
|
->setOrder('model_name', 'ASC')
|
|
->setOrder('file_name', 'ASC');
|
|
|
|
foreach ($collection as $file) {
|
|
$filePath = (string)$file->getData('file_path');
|
|
if ($filePath === '' || !$this->media->exists($filePath)) {
|
|
$missingCount++;
|
|
continue;
|
|
}
|
|
|
|
$modelName = $this->sanitizeArchiveSegment((string)$file->getData('model_name'), 'model');
|
|
$fileName = $this->sanitizeArchiveSegment((string)$file->getData('file_name'), 'specification');
|
|
$entryName = $brandDirectoryName . '/' . $modelName . '/' . $fileName;
|
|
if (isset($entryNames[$entryName])) {
|
|
$entryName = $brandDirectoryName . '/' . $modelName . '/'
|
|
. pathinfo($fileName, PATHINFO_FILENAME) . '-' . (int)$file->getId()
|
|
. ($this->getExtension($fileName) !== '' ? '.' . $this->getExtension($fileName) : '');
|
|
}
|
|
|
|
if ($zip->addFile($this->media->getAbsolutePath($filePath), $entryName)) {
|
|
$entryNames[$entryName] = true;
|
|
$fileCount++;
|
|
} else {
|
|
$missingCount++;
|
|
}
|
|
}
|
|
} finally {
|
|
$closed = $zip->close();
|
|
}
|
|
} catch (\Throwable $exception) {
|
|
if ($mediaDirectory->isExist($temporaryPath)) {
|
|
$mediaDirectory->delete($temporaryPath);
|
|
}
|
|
throw $exception;
|
|
}
|
|
|
|
if (!$closed || $fileCount === 0) {
|
|
if ($mediaDirectory->isExist($temporaryPath)) {
|
|
$mediaDirectory->delete($temporaryPath);
|
|
}
|
|
throw new LocalizedException(
|
|
__('No readable specification files were available for brand "%1".', $brand->getData('brand_name'))
|
|
);
|
|
}
|
|
|
|
if ($mediaDirectory->isExist($archivePath)) {
|
|
$mediaDirectory->delete($archivePath);
|
|
}
|
|
$mediaDirectory->renameFile($temporaryPath, $archivePath);
|
|
|
|
if ($currentPath !== '' && $currentPath !== $archivePath) {
|
|
$this->media->delete($currentPath);
|
|
}
|
|
$this->saveImport($import, $brandId, $archivePath, $fileCount);
|
|
|
|
return [
|
|
'status' => 'rebuilt',
|
|
'path' => $archivePath,
|
|
'file_count' => $fileCount,
|
|
'missing_count' => $missingCount,
|
|
];
|
|
}
|
|
|
|
private function getLatestImport(int $brandId): ?SpecificationImport
|
|
{
|
|
$import = $this->importCollectionFactory->create()
|
|
->addFieldToFilter('brand_id', $brandId)
|
|
->setOrder('updated_at', 'DESC')
|
|
->setPageSize(1)
|
|
->getFirstItem();
|
|
|
|
return $import->getId() ? $import : null;
|
|
}
|
|
|
|
private function saveImport(
|
|
?SpecificationImport $import,
|
|
int $brandId,
|
|
string $archivePath,
|
|
int $fileCount
|
|
): void {
|
|
$import ??= $this->importFactory->create();
|
|
$import->setData('brand_id', $brandId);
|
|
$import->setData('archive_file', $archivePath);
|
|
$import->setData('file_count', $fileCount);
|
|
$import->setData('status', 1);
|
|
$this->importResource->save($import);
|
|
}
|
|
|
|
private function sanitizeArchiveSegment(string $value, string $fallback): string
|
|
{
|
|
$value = trim(str_replace(['/', '\\'], '_', $value));
|
|
$value = preg_replace('/[\x00-\x1F\x7F]+/u', '_', $value) ?: '';
|
|
$value = trim($value, " .\t\n\r\0\x0B");
|
|
|
|
return $value !== '' ? $value : $fallback;
|
|
}
|
|
|
|
private function getExtension(string $fileName): string
|
|
{
|
|
return (string)pathinfo($fileName, PATHINFO_EXTENSION);
|
|
}
|
|
}
|