first commit

This commit is contained in:
2025-08-02 16:30:27 +02:00
commit 23646bfcee
14851 changed files with 1750626 additions and 0 deletions

791
class/class.Photos.php Normal file
View File

@@ -0,0 +1,791 @@
<?php
use KupShop\CDNBundle\CDN;
use KupShop\KupShopBundle\Config;
use KupShop\KupShopBundle\Context\LanguageContext;
use KupShop\KupShopBundle\Util\Compat\ServiceContainer;
use KupShop\KupShopBundle\Util\Contexts;
use Query\Operator;
class PhotosBase
{
use DatabaseCommunication;
public const PHOTO_VERSION_DESKTOP = '2';
public const PHOTO_VERSION_TABLET = 'tablet';
public const PHOTO_VERSION_MOBILE = 'mobile';
public const PHOTO_VERSIONS = [
self::PHOTO_VERSION_DESKTOP,
self::PHOTO_VERSION_TABLET,
self::PHOTO_VERSION_MOBILE,
];
// typ obrazku se kterym se pracuje
public $photoType = 'photo';
// cesta adresare fotek
public $photosPath = '';
// relativni cesta z data/photos
public $relPhotoPath;
// verze fotky (desktop, tablet, mobil)
public $photoVersion = self::PHOTO_VERSION_DESKTOP;
public $ID;
// informace o obrazku
public $image = [];
public function __construct($type = 'product')
{
$this->photoType = $type;
$this->photosPath = $GLOBALS['cfg']['Path']['photos'];
return true;
}
public static function get(?int $id = null, string $version = self::PHOTO_VERSION_DESKTOP, string $type = 'none'): self
{
$self = new static($type);
$self->newImage($id);
$self->photoVersion = $version;
if ($id !== null) {
$photo = sqlQueryBuilder()
->select('id, source')
->from('photos')
->where(Operator::equals(['id' => $id]))
->execute()->fetch();
if ($photo) {
$pathFinder = \KupShop\KupShopBundle\Util\System\PathFinder::getService();
$self->photosPath = $pathFinder->dataPath('photos/'.$photo['source']);
$self->relPhotoPath = $photo['source'];
}
}
return $self;
}
public function newImage($newID = null)
{
switch ($this->photoType) {
case 'section':
case 'producer':
case 'articles_authors':
case 'delivery':
case 'return_delivery':
case 'payment':
$this->photosPath = $GLOBALS['cfg']['Path']['data'].$this->photoType.'/';
$this->ID = $newID;
break;
case 'section_additional':
case 'product':
case 'article':
case 'producers':
case 'page':
case 'slider':
case 'none':
default:
$this->photosPath = $GLOBALS['cfg']['Path']['photos'];
if (is_null($newID)) {
sqlQuery('INSERT INTO photos (id) VALUES (NULL)');
$newID = sqlInsertId();
}
$this->ID = $newID;
break;
}
$this->image = [];
}
public function createDir($path)
{
if (!file_exists($path)) {
if (!@mkdir($path, 0777)) {
// zalogovat chybu
if (function_exists('logError')) {
logError(__FILE__, __LINE__, 'ERROR MAKE DIR FOR PHOTOS (MKDIR): '.$path);
}
throw new Exception('Nepovedlo se vytvořit nový adresář pro fotky (CHMOD)'.$path);
}
@chmod($path, 0777);
}
}
public function uploadImage($picture, $from_http = true, $copy = false, ?string $language = null)
{
if (!$picture) {
return false;
}
$img = [
'format' => 'NONE',
'original_filename' => '',
'filename' => '',
'path' => '',
'relpath' => '',
'suffix' => '',
'width' => 0,
'height' => 0,
'newWidth' => 0,
'newHeight' => 0,
];
// koncovka
$img['suffix'] = '.'.pathinfo($picture['name'], PATHINFO_EXTENSION);
$img['original_filename'] = $picture['name'];
// format obrazku [JPEG, GIF, PNG, SVG, WEBP, AVIF]
try {
$image = new Imagick($picture['tmp_name']);
} catch (ImagickException $e) {
try {
$image = new Imagick('svg:'.$picture['tmp_name']);
} catch (ImagickException $e) {
return false;
}
}
$img['format'] = $image->getImageFormat();
if (!$img['format']) {
logError(__FILE__, __LINE__, 'Neznamy format obrazku - '.print_r($picture, true));
return false;
}
// hack
if ($img['format'] == 'MVG') {
$img['format'] = 'SVG';
}
// if (empty($img['suffix']))
// Force file suffix
$img['suffix'] = '.'.strtolower($img['format']);
// add photo language
if ($language !== null) {
$img['suffix'] = '.'.$language.$img['suffix'];
}
// add photo version (tablet, mobile) to file
if ($this->photoVersion !== self::PHOTO_VERSION_DESKTOP) {
$img['suffix'] = '.'.$this->photoVersion.$img['suffix'];
}
$img['filename'] = 'p'.$this->ID.$img['suffix'];
$img['path'] = $this->photosPath;
$img['relpath'] = $this->relPhotoPath ?? '';
if ($this->photosPath == $GLOBALS['cfg']['Path']['photos']) {
// rok
$img['path'] .= date('Y').'/';
$img['relpath'] .= date('Y').'/';
$this->createDir($img['path']);
// mesic
$img['path'] .= date('m').'/';
$img['relpath'] .= date('m').'/';
}
$this->createDir($img['path']);
// ulozit obrazek na server
if ($from_http) {
$ret = move_uploaded_file($picture['tmp_name'], $img['path'].$img['filename']);
} else {
if ($copy) {
$ret = copy($picture['tmp_name'], $img['path'].$img['filename']);
} else {
$ret = rename($picture['tmp_name'], $img['path'].$img['filename']);
}
}
if (!$ret) {
// zalogovat chybu
logError(__FILE__, __LINE__, 'ERROR SAVE PHOTO FILE: '.$img['path'].$img['filename']);
throw new Exception('Nepodařilo se uložit fotku na server (move_uploaded_file '.$img['path'].$img['filename'].')');
}
$this->image = $img;
return $this->resizeImage(4000, 4000);
}
public function resizeImage($sizeX, $sizeY, $direction = 'scale_down')
{
if ($this->image['format'] == 'NONE') {
return false;
}
if ($this->image['format'] == 'SVG') {
return true;
}
$src = realpath($this->image['path'].$this->image['filename']);
$thumbnail = new Imagick($src);
// kdyz se nemeni rozmery ukoncit
$size = $thumbnail->getImageGeometry();
$orientation = $thumbnail->getImageOrientation();
$resize = $size['width'] > $sizeX || $size['height'] > $sizeY;
$rotate = $orientation !== Imagick::ORIENTATION_TOPLEFT;
if ($rotate) {
// set proper orientation
$angle = false;
$flip = false;
$flop = false;
switch ($orientation) {
case imagick::ORIENTATION_BOTTOMRIGHT:
$angle = 180;
break;
case imagick::ORIENTATION_RIGHTTOP:
$angle = 90;
break;
case imagick::ORIENTATION_LEFTBOTTOM:
$angle = -90;
break;
case imagick::ORIENTATION_LEFTTOP:
$angle = 90;
$flop = true;
break;
default:
$rotate = false; // Kdyz jde o nepodchyceny stav nepreulozime
break;
}
if ($angle) {
$thumbnail->rotateimage('#000', $angle); // rotate 90 degrees CW
}
if ($flip) {
$thumbnail->flipImage();
}
if ($flop) {
$thumbnail->flopImage();
}
if ($rotate) {
$thumbnail->setImageOrientation(Imagick::ORIENTATION_TOPLEFT);
}
}
if ($resize) {
$thumbnail->resizeImage($sizeX, $sizeY, Imagick::FILTER_CATROM, 1, true);
}
if (!$resize && !$rotate) {
return true;
}
// Save image using proper image function
switch ($thumbnail->getImageFormat()) {
case 'GIF':
$thumbnail->setImageFormat('gif');
break;
case 'PNG':
$thumbnail->setImageFormat('png');
break;
case 'WEBP':
case 'JPG':
case 'JPEG':
$thumbnail->setImageFormat('webp');
$thumbnail->setOption('webp:lossless', 'false');
$thumbnail->setOption('webp:use-sharp-yuv', '1');
$thumbnail->setImageCompressionQuality(90);
break;
case 'AVIF':
$thumbnail->setImageFormat('avif');
break;
default:
wpj_debug('ERROR!! Unsupported OUTPUT image type!');
}
$thumbnail->stripImage();
$thumbnail->writeImage("{$thumbnail->getImageFormat()}:{$src}");
// Change attributes, rwx+ugo
chmod($src, 0777);
$thumbnail->destroy();
return true;
}
public function checkFileSize($maxSize, $imgInputSize)
{
if ($imgInputSize > $maxSize) {
return false;
} else {
return true;
}
}
public function checkFileType()
{
$imageType = '';
if (preg_match('/jpeg|jfif|jpg/i', $this->image['format'])) {
$imageType = 'JPEG';
}
if (preg_match('/png/i', $this->image['format'])) {
$imageType = 'PNG';
}
if (preg_match('/gif/i', $this->image['format'])) {
$imageType = 'GIF';
}
if (preg_match('/svg/i', $this->image['format'])) {
$imageType = 'SVG';
}
if ($imageType == '') {
return false;
} else {
return true;
}
}
public function insertImageIntoDB($description = '', $author = '', $date_added = null, $sync_id = null)
{
if (empty($date_added)) {
$date_added = date('c');
}
switch ($this->photoType) {
case 'section':
$SQL = $this->updateSQL('sections', ['photo' => $this->image['filename'], 'date_updated' => $date_added], ['id' => $this->ID]);
break;
case 'articles_authors':
$SQL = $this->updateSQL('articles_authors', ['photo' => $this->image['filename']], ['id' => $this->ID]);
break;
case 'producer':
$SQL = $this->updateSQL('producers', ['photo' => $this->image['filename'], 'date_updated' => $date_added], ['id' => $this->ID]);
break;
case 'delivery':
$SQL = $this->updateSQL('delivery_type_delivery', ['photo' => $this->image['filename'], 'date_updated' => $date_added], ['id' => $this->ID]);
break;
case 'return_delivery':
$SQL = $this->updateSQL('return_delivery', ['photo' => $this->image['filename']], ['id' => $this->ID]);
break;
case 'payment':
$SQL = $this->updateSQL('delivery_type_payment', ['photo' => $this->image['filename'], 'date_updated' => $date_added], ['id' => $this->ID]);
break;
case 'section_additional':
case 'article':
case 'product':
case 'producers':
case 'page':
case 'slider':
case 'none':
default:
sqlQueryBuilder()
->insert('photos')
->directValues(
[
'id' => $this->ID,
'descr' => $description,
'filename' => $this->image['original_filename'],
'source' => $this->image['relpath'],
'image_'.$this->photoVersion => $this->image['filename'],
'sync_id' => $sync_id ?? $description,
'date' => $date_added,
]
)
->onDuplicateKeyUpdate(
[
'id' => 'LAST_INSERT_ID(id)',
'descr',
'filename',
'source',
'image_'.$this->photoVersion,
'sync_id',
'date',
'date_update' => 'VALUES(date)',
]
)
->execute();
$this->ID = sqlInsertId();
break;
}
$this->clearThumbnails();
return $this->ID;
}
public function getID()
{
return $this->ID;
}
public function insertRelation($table, $id_photo, $main_field, $main_value, $showInLead)
{
if ($showInLead == 'Y') {
sqlQueryBuilder()
->update($table)
->directValues(['show_in_lead' => 'N'])
->where(\Query\Operator::equals([$main_field => $main_value, 'show_in_lead' => 'Y']))
->execute();
}
sqlQueryBuilder()
->insert($table)
->values([
'id_photo' => $id_photo,
$main_field => $main_value,
'show_in_lead' => "'{$showInLead}'",
'active' => "'Y'",
'date_added' => 'NOW()', ])
->execute();
static::checkLeadPhoto($table, $main_field, $main_value);
}
public function insertProductRelation($IDphoto, $IDproduct, $showInLead, $active, $id_variation = null)
{
// Ignore if this relation already exists - avoid duplicates in photos_products_relations and lost of position
$exists = returnSQLResult('SELECT COUNT(*) FROM photos_products_relation WHERE id_photo=:id_photo AND id_product=:id_product',
['id_photo' => $IDphoto, 'id_product' => $IDproduct]);
if ($exists > 0) {
return;
}
$this->insertSQL('photos_products_relation', [
'id_photo' => $IDphoto,
'id_product' => $IDproduct,
'show_in_lead' => $showInLead,
'active' => $active,
'date_added' => new \DateTime(),
'position' => 10000,
], [], ['date_added' => 'date']);
if ($id_variation) {
$this->insertSQL('photos_products_relation', [
'id_photo' => $IDphoto,
'id_product' => $IDproduct,
'id_variation' => $id_variation,
'show_in_lead' => $showInLead,
'active' => $active,
'date_added' => new \DateTime(),
'position' => 10000,
], [], ['date_added' => 'date']);
}
static::checkLeadPhoto('photos_products_relation', 'id_product', $IDproduct);
}
public function erasePhoto($IDph)
{
$row = sqlQueryBuilder()
->select('source, image_2, image_tablet, image_mobile')
->from('photos')
->where(Operator::equals(['id' => $IDph]))
->execute()->fetch();
if ($row) {
$sameFileCount = returnSQLResult('SELECT COUNT(*) FROM '.getTableName('photos').' WHERE '.selectQueryCreate($row, true));
if ($sameFileCount <= 1) {
$src = $this->photosPath.$row['source'];
foreach (self::PHOTO_VERSIONS as $version) {
$field = 'image_'.$version;
if (!empty($row[$field]) && file_exists($src.$row[$field])) {
if (!@unlink($src.$row[$field])) {
logError(__FILE__, __LINE__, 'ERROR DELETE PHOTO FILE: '.$src.$row[$field]);
}
}
}
if (findModule(Modules::VIDEOS)) {
$video = sqlQueryBuilder()->select('v.filename video, id_cdn')
->from('photos', 'p')
->innerJoin('p', 'videos', 'v', 'p.id = v.id_photo')
->where(Operator::equals(['p.id' => $IDph]))
->execute()
->fetch();
if ($video) {
$src = 'data/videos/'.$row['source'];
if (!empty($video['video']) && file_exists($src.$video['video'])) {
if (!@unlink($src.$video['video'])) {
logError(__FILE__, __LINE__, 'ERROR DELETE VIDEO FILE: '.$src.$video['video']);
}
}
ServiceContainer::getService(CDN::class)->deleteVideo($video['id_cdn']);
}
}
}
$result = sqlQueryBuilder()
->delete('photos')
->where(Operator::equals(['id' => $IDph]))
->execute();
if ($result) {
return true;
} else {
return false;
}
} else {
return false;
}
}
public function erasePhotoVersion(int $IDph, string $version)
{
if (!in_array($version, self::PHOTO_VERSIONS)) {
return false;
}
$row = sqlQueryBuilder()
->select('source, image_2, image_tablet, image_mobile')
->from('photos')
->where(Operator::equals(['id' => $IDph]))
->execute()->fetch();
if ($row) {
$src = $this->photosPath.$row['source'];
$field = 'image_'.$version;
if (!empty($row[$field])) {
@unlink($src.$row[$field]);
sqlQueryBuilder()
->update('photos')
->directValues([$field => null])
->where(Operator::equals(['id' => $IDph]))
->execute();
$this->clearThumbnails();
return true;
}
}
return false;
}
public function deletePhoto($update_db = true)
{
switch ($this->photoType) {
case 'producer':
$src = returnSQLResult('SELECT photo FROM '.getTableName('producers')." WHERE id='{$this->ID}'");
break;
case 'section':
$src = returnSQLResult('SELECT photo FROM '.getTableName('sections')." WHERE id='{$this->ID}'");
break;
case 'articles_authors':
$src = returnSQLResult('SELECT photo FROM '.getTableName('articles_authors')." WHERE id='{$this->ID}'");
break;
case 'return_delivery':
$src = returnSQLResult('SELECT photo FROM '.getTableName('return_delivery')." WHERE id='{$this->ID}'");
break;
case 'delivery':
$src = returnSQLResult('SELECT photo FROM '.getTableName('delivery_type_delivery')." WHERE id='{$this->ID}'");
break;
case 'payment':
$src = returnSQLResult('SELECT photo FROM '.getTableName('delivery_type_payment')." WHERE id='{$this->ID}'");
break;
case 'section_additional':
case 'product':
case 'article':
case 'producers':
case 'page':
case 'slider':
case 'none':
default:
return $this->erasePhoto($this->ID);
break;
}
if (!empty($src)) {
$src = $this->photosPath.$src;
if (file_exists($src)) {
if (!unlink($src)) {
// zalogovat chybu
if (function_exists('logError')) {
logError(__FILE__, __LINE__, 'ERROR DELETE PRODUCER PHOTO FILE: '.$src);
}
}
}
}
if ($update_db) {
switch ($this->photoType) {
case 'producer':
sqlQuery('UPDATE '.getTableName('producers')." SET photo='' WHERE id='{$this->ID}' ");
break;
case 'section':
sqlQuery('UPDATE '.getTableName('sections')." SET photo='' WHERE id='{$this->ID}' ");
break;
case 'articles_authors':
sqlQuery('UPDATE '.getTableName('articles_authors')." SET photo='' WHERE id='{$this->ID}' ");
break;
case 'return_delivery':
sqlQuery("UPDATE return_delivery SET photo='' WHERE id='{$this->ID}' ");
break;
case 'delivery':
sqlQuery('UPDATE '.getTableName('delivery_type_delivery')." SET photo='' WHERE id='{$this->ID}' ");
break;
case 'payment':
sqlQuery('UPDATE '.getTableName('delivery_type_payment')." SET photo='' WHERE id='{$this->ID}' ");
break;
default:
logError(__FILE__, __LINE__, 'Unknown photo type: '.$this->photoType);
}
}
$this->clearThumbnails();
return true;
}
/**
* @param int[] $types array of size IDs to clear
*
* @return bool
*/
public function clearThumbnails(?array $types = null)
{
global $cfg;
$languages = array_keys(Contexts::get(LanguageContext::class)->getSupported());
$languages[] = null;
if (!$types) {
$types = array_keys($cfg['Photo']['id_to_type']);
}
// Stupid simple way - delete all thumbnails of given ID
foreach ($types as $id) {
foreach ($languages as $language) {
foreach (self::PHOTO_VERSIONS as $version) {
@unlink(getImagePath($this->getID(), $id, 'jpg', $language, $version));
@unlink(getImagePath($this->getID(), $id, 'png', $language, $version));
}
}
}
return true;
}
/** Makes sure there is one lead photo and correct photo ordering.
*/
public static function checkLeadPhoto($table, $id_field, $id)
{
sqlQuery("SELECT @i := -1; UPDATE {$table} SET position = (select @i := @i + 1) WHERE {$id_field}=:id ORDER BY position, id_photo", ['id' => $id]);
sqlQuery("UPDATE {$table} SET show_in_lead=IF(position=0, 'Y', IF(show_in_lead='Y', 'N', show_in_lead)) WHERE {$id_field}=:id", ['id' => $id]);
}
public function uploadPhotoOrVideo($image, $filename)
{
$cfg = Config::get();
$video = null;
if (strpos($image['type'], 'video') !== false) {
$video = $image;
$image['tmp_name'] = 'engine/admin/static/images/img_gen.png';
}
$this->uploadImage($image, $video === null, $video !== null);
if ($photoId = $this->insertImageIntoDB()) {
if ($video) {
$video_filename = $photoId.'.'.pathinfo($filename, PATHINFO_EXTENSION);
$this->createDir('data/videos/');
$path = 'data/videos/'.date('Y').'/';
$this->createDir($path);
$path .= date('m').'/';
$this->createDir($path);
move_uploaded_file($video['tmp_name'], $path.$video_filename);
$cdn = ServiceContainer::getService(KupShop\CDNBundle\CDN::class);
$response_create = $cdn->createVideo($video_filename);
if (!isset($response_create['guid'])) {
return false;
}
$response_fetch = $cdn->fetchVideo($response_create['guid'], $cfg['Addr']['full']."{$path}{$video_filename}");
getLogger()->notice('Video upload response', ['response' => $response_fetch, 'file' => $cfg['Addr']['full']."{$path}{$video_filename}"]);
if (!isset($response_fetch['success']) || !$response_fetch['success']) {
return false;
}
sqlQueryBuilder()->insert('videos')->directValues([
'id_photo' => $photoId,
'id_cdn' => $response_create['guid'],
'filename' => $video_filename,
])->execute();
}
$this->updateSQL('photos', ['filename' => $filename], ['id' => $photoId]);
}
}
public function refreshVideoThumbnail($video): bool
{
$cdn = ServiceContainer::getService(CDN::class);
$thumbnail = $cdn->getThumbnail($video['id_video']);
/** Nahrazení obrázku */
$downloader = new Downloader();
$downloader->setCurlHeader('Referer: https://panel.bunny.net');
$downloader->setMethod('curl');
if (!$this->uploadImage($downloader->downloadImage($thumbnail['url']), false)) {
return false;
}
$this->insertImageIntoDB($video['descr']);
// Keep filename, overwritten by uploadImage()
sqlQueryBuilder()
->update('photos')
->directValues(['filename' => $video['filename']])
->where(Operator::equals(['id' => $video['id']]))
->execute();
return true;
}
public function updateDateUpdated()
{
sqlQueryBuilder()->update('photos')->set('date_update', 'DEFAULT')
->where(Operator::equals(['id' => $this->ID]))
->execute();
}
}
if (empty($subclass)) {
class Photos extends PhotosBase
{
}
}