Files
kupshop/bundles/External/HannahBundle/Resources/script/ImportPhotosScript.php
2025-08-02 16:30:27 +02:00

233 lines
8.3 KiB
PHP

<?php
declare(strict_types=1);
namespace External\HannahBundle\Resources\script;
use Doctrine\DBAL\Exception\DeadlockException;
use Doctrine\DBAL\Exception\LockWaitTimeoutException;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use External\HannahBundle\Util\FTP\FTPClient;
use External\HannahBundle\Util\ProductUtil;
use KupShop\AdminBundle\Util\Script\Script;
use KupShop\KupShopBundle\Util\Compat\ServiceContainer;
use KupShop\KupShopBundle\Util\System\PathFinder;
use Psr\Log\LoggerInterface;
use Query\Operator;
class ImportPhotosScript extends Script
{
protected static $name = 'Importovat fotky k produktům z FTP';
protected static $defaultParameters = [
'productIds' => [],
// Pokud true, tak se doimportuji chybejici obrazky
'update' => false,
];
protected function run(array $arguments)
{
$productUtil = ServiceContainer::getService(ProductUtil::class);
$qb = sqlQueryBuilder()
->select('p.id, p.code, JSON_VALUE(p.data, "$.photosHash") as photosHash, COUNT(ppr.id_photo) as photosCount')
->from('products', 'p')
->leftJoin('p', 'photos_products_relation', 'ppr', 'p.id = ppr.id_product')
// ignore outfits
->andWhere(Operator::not(Operator::findInSet(['OF'], 'p.campaign')))
->groupBy('p.id');
// ignore outfits if labels are used
if ($outfitLabelId = $productUtil->getLabelByCode('OF')) {
$qb->leftJoin('p', 'product_labels_relation', 'plr', 'plr.id_product = p.id AND plr.id_label = :outfitLabelId')
->andWhere('plr.id_label IS NULL')
->setParameter('outfitLabelId', $outfitLabelId);
}
if (!($arguments['update'] ?? false)) {
$qb->andWhere('ppr.id_photo IS NULL');
}
if ($arguments['productIds'] ?? false) {
$qb->andWhere(Operator::inIntArray((array) $arguments['productIds'], 'p.id'));
}
foreach ($qb->execute() as $item) {
if (empty($this->getPhotoDownloader()->getPhotosCache())) {
// pravdepodobne se nepodarilo naloadovat obrazky z FTP, tak at neodeberu vsem produktum fotky
continue;
}
$originalCode = $item['code'];
$code = $originalCode;
// pokud ma kod 11 znaku, tak zkusim ke kodu doplnit 00, aby mel 13 znaku
if (strlen($originalCode) == 11) {
$code .= '00';
}
// zkusim najit fotky podle 13 mistneho kodu
if (!($photos = $this->getPhotoDownloader()->getProductPhotos($code))) {
// pokud nenajdu fotky, tak zkusim najit fotky podle originalniho kodu
if (!($photos = $this->getPhotoDownloader()->getProductPhotos($originalCode))) {
// pokud nenajdu fotky ani podle originalniho kodu, tak zkusim najit fotky podle originalniho kodu + 01 na konci
$photos = $this->getPhotoDownloader()->getProductPhotos($originalCode.'01');
}
}
$photosHash = md5(implode('-', $photos));
$invalidHash = !empty($photos) && $item['photosCount'] <= 0 && !empty($item['photosHash']);
// Stejny fotky, nemam co aktualizovat
if ($photosHash === $item['photosHash'] && !$invalidHash) {
continue;
}
$this->withRetryStrategy(fn () => $this->updatePhotos((int) $item['id'], $photos));
// Ulozit photosHash
sqlQueryBuilder()
->update('products')
->set('data', 'JSON_SET(COALESCE(data, "{}"), "$.photosHash", :value)')
->setParameter('value', $photosHash)
->where(Operator::equals(['id' => $item['id']]))
->execute();
}
}
private function updatePhotos(int $productId, array $photos): void
{
$photoRelations = [];
foreach ($photos as $index => $photo) {
$dest = $this->getPathFinder()->dataPath('photos/old'.$photo);
$pathInfo = pathinfo($dest);
if (!file_exists($pathInfo['dirname'])) {
mkdir($pathInfo['dirname'], 0777, true);
}
$photoSource = trim(str_replace('data/photos/', '', $pathInfo['dirname']), '/').'/';
$photoName = $pathInfo['basename'];
if (file_exists($dest) || $this->getPhotoDownloader()->copyFile($photo, $dest)) {
$photoId = sqlQueryBuilder()
->select('id')
->from('photos')
->where(Operator::equals(
[
'filename' => $photoName,
'source' => $photoSource,
]
))
->execute()->fetchOne();
if (!$photoId) {
$photoId = sqlGetConnection()->transactional(function () use ($photoName, $photoSource) {
sqlQueryBuilder()
->insert('photos')
->directValues(
[
'filename' => $photoName,
'source' => $photoSource,
'image_2' => $photoName,
'date' => (new \DateTime())->format('Y-m-d H:i:s'),
]
)->execute();
return (int) sqlInsertId();
});
}
// Zarazeni provedu az pozdeji
$photoRelations[$photoId] = $index;
}
}
// pokud u produktu podle FTP nemam zadne fotky, tak nic nedelam, abych produktu nesmazal fotky
if (empty($photoRelations)) {
$this->getLogger()->notice('[OC] Empty photos update', [
'productId' => $productId,
'photos' => $photos,
'photoRelations' => $photoRelations,
]);
return;
}
// Aktualizovat photos_products_relation
sqlGetConnection()->transactional(function () use ($photoRelations, $productId) {
sqlQueryBuilder()
->delete('photos_products_relation')
->where(Operator::equals(['id_product' => $productId]))
->execute();
foreach ($photoRelations as $photoId => $position) {
try {
sqlQueryBuilder()
->insert('photos_products_relation')
->directValues(
[
'id_photo' => $photoId,
'id_product' => $productId,
'id_variation' => null,
'show_in_lead' => $position === 0 ? 'Y' : 'N',
'position' => $position,
'date_added' => (new \DateTime())->format('Y-m-d H:i:s'),
]
)->execute();
} catch (UniqueConstraintViolationException $e) {
}
}
});
}
private function withRetryStrategy(callable $fn, int $maxTries = 5, int $currentTry = 0): void
{
try {
$fn();
} catch (LockWaitTimeoutException|DeadlockException $e) {
if ($currentTry >= $maxTries) {
return;
}
sleep(1);
$this->withRetryStrategy($fn, $maxTries, ++$currentTry);
}
}
private function getPathFinder(): PathFinder
{
static $pathFinder;
if (!$pathFinder) {
$pathFinder = ServiceContainer::getService(PathFinder::class);
}
return $pathFinder;
}
private function getLogger(): LoggerInterface
{
static $logger;
if (!$logger) {
$logger = ServiceContainer::getService('logger');
}
return $logger;
}
private function getPhotoDownloader(): FTPClient
{
static $photoDownloader;
if (!$photoDownloader) {
$photoDownloader = ServiceContainer::getService(FTPClient::class);
}
return $photoDownloader;
}
}
return ImportPhotosScript::class;