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 { } }