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); } }