Files
kupshop/bundles/KupShop/ContentBundle/Controller/BlockController.php
2025-08-02 16:30:27 +02:00

572 lines
21 KiB
PHP

<?php
namespace KupShop\ContentBundle\Controller;
use KupShop\AdminBundle\Util\BlocksHistory;
use KupShop\CDNBundle\CDN;
use KupShop\ContentBundle\Util\BlocekSettings;
use KupShop\ContentBundle\Util\Block;
use KupShop\ContentBundle\View\BlockEditView;
use KupShop\ContentBundle\View\BlockHistoryView;
use KupShop\I18nBundle\Translations\PhotosTranslation;
use KupShop\KupShopBundle\Query\JsonOperator;
use KupShop\KupShopBundle\Util\Functional\Mapping;
use KupShop\KupShopBundle\Util\Logging\SentryLogger;
use KupShop\KupShopBundle\Util\StringUtil;
use KupShop\KupShopBundle\Util\System\PathFinder;
use Query\Operator;
use Query\QueryBuilder;
use Query\Translation;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Routing\Annotation\Route;
class BlockController extends AbstractController
{
use \DatabaseCommunication;
public function __construct(protected ?CDN $cdn = null)
{
}
/**
* @Route("/_blocek/upload/")
*/
public function uploadAction(Request $request)
{
try {
$this->doCheckPermissions();
} catch (\Exception $e) {
return new JsonResponse([
'result' => false,
'error' => $e->getMessage(),
]);
}
$result = false;
$message = null;
try {
$result = sqlGetConnection()->transactional(
function () use ($request) {
$uploadedIds = [];
$photos = new \Photos('none');
/** @var UploadedFile $file */
foreach ($request->files->get('file', []) as $file) {
$data = [
'name' => $file->getClientOriginalName(),
'tmp_name' => $file->getRealPath(),
];
$photos->newImage();
$photos->uploadImage($data);
$photoId = $photos->insertImageIntoDB();
if (!$photoId) {
throw new \Exception('Nahrání se nepodařilo, zkuste to znovu.');
}
$uploadedIds[] = $photoId;
}
return $uploadedIds;
}
);
} catch (\Exception $e) {
$message = $e->getMessage();
}
return new JsonResponse([
'result' => $result,
'message' => $message,
]);
}
#[Route('/_blocek/video-info/')]
public function videoInfoAction(Request $request, PathFinder $pathFinder): JsonResponse
{
$result = false;
$message = null;
$data = [];
try {
if (!findModule(\Modules::CDN) || !$this->cdn) {
throw new \Exception('Není povolen modul CDN!');
}
$videos = $this->getPhotosData((array) $request->get('videoId', []), function (QueryBuilder $qb) {
$qb->leftJoin('ph', 'videos', 'v', 'ph.id = v.id_photo');
$qb->addSelect('v.*');
});
foreach ($videos as $video) {
if (!$video['id_cdn']) {
throw new \Exception('Video není nahráno v CDN!');
}
$data[] = [
'id' => (int) $video['id'],
'title' => $video['descr'],
'id_cdn' => $video['id_cdn'],
'url_mp4' => $this->cdn->getMP4Video($video['id_cdn'], '720p'),
'url_hls' => $this->cdn->getHLSPlaylist($video['id_cdn']),
'thumbnail' => $this->cdn->getThumbnail($video['id_cdn'])['url'],
];
}
$result = true;
} catch (\Exception $e) {
$message = $e->getMessage();
$data = [];
}
return $this->jsonResponse(result: $result, data: $data, message: $message);
}
/**
* @Route("/_blocek/photo-info/")
*/
public function photoInfoAction(Request $request, PathFinder $pathFinder): JsonResponse
{
$result = false;
$message = null;
$data = [];
try {
foreach ($this->getPhotosData((array) $request->get('photoId', [])) as $photo) {
$file = $pathFinder->getDataDir().'photos/'.$photo['source'].$photo['image_2'];
if (!file_exists($file)) {
throw new \Exception('Obrázek nebyl nalezen!');
}
[$width, $height] = getimagesize($file);
$data[] = [
'id' => (int) $photo['id'],
'width' => $width,
'height' => $height,
'timestamp' => filemtime($file),
'title' => $photo['descr'],
];
}
$result = true;
} catch (\Exception $e) {
$message = $e->getMessage();
$data = [];
}
return $this->jsonResponse(result: $result, data: $data, message: $message);
}
private function jsonResponse(bool $result, array $data, ?string $message = null): JsonResponse
{
return new JsonResponse(
[
'result' => $result,
'message' => $message,
'data' => $data,
],
200,
[
'Access-Control-Allow-Origin' => '*',
]
);
}
private function getPhotosData(array $ids, ?callable $specs = null): array
{
$photos = sqlQueryBuilder()
->select('ph.*')
->from('photos', 'ph')
->andWhere(Operator::inIntArray($ids, 'ph.id'))
->andWhere(Translation::coalesceTranslatedFields(PhotosTranslation::class));
if ($specs) {
$photos->andWhere($specs);
}
return $photos->execute()->fetchAllAssociative();
}
/**
* @Route("/_blocek/edit-block/{id}/")
*/
public function editBlockAction(Request $request, Block $block, BlockEditView $blockEditView, $id)
{
$blockEditView->setBlockId($id);
return $blockEditView->getResponse();
}
/**
* @Route("/_blocek/history-block/{historyId}/{blockId}/")
*/
public function historyBlockAction(Request $request, Block $block, BlockHistoryView $blockHistoryView, $historyId, $blockId)
{
$blockHistoryView->setBlockId($blockId);
$blockHistoryView->setHistoryId($historyId);
return $blockHistoryView->getResponse();
}
/**
* @Route("/_blocek/save-block/")
*/
public function saveBlockAction(Request $request, SentryLogger $sentryLogger, BlocksHistory $blocksHistory, Block $blockUtil)
{
try {
$this->doCheckPermissions();
} catch (\Exception $e) {
return new JsonResponse([
'result' => false,
'error' => $e->getMessage(),
]);
}
$data_array = json_decode($request->getContent(), true);
try {
$result = sqlGetConnection()->transactional(
function () use ($blockUtil, $data_array, $blocksHistory) {
foreach ($data_array as $data) {
if ($data['id_block'] = $this->getBlockId($blockUtil,
$data['id_block'] ?: null,
$data['object_type'] ?: null,
$data['object_id'] ?: null)) {
// get images blocks
$imagesBlocks = $this->getImageBlocks(json_decode($data['json_content'], true));
$language = ($data['id_language'] ?? null);
$data['json_content'] = json_encode(json_decode($data['json_content'], true));
$data['content'] = StringUtil::unicodeToEntities($data['content']);
if (!$language) {
$block = ['id' => $data['id_block'], 'name' => '', 'content' => $data['content']];
$blocksHistory->saveBlocksHistory([$data['id_block'] => $block]);
// save content
sqlQueryBuilder()->update('blocks')
->directValues(
[
'json_content' => $data['json_content'],
'content' => $data['content'],
]
)
->where(Operator::equals(['id' => $data['id_block']]))
->execute();
$rootId = sqlQueryBuilder()->select('id_root')->from('blocks')
->where(Operator::equals(['id' => $data['id_block']]))
->execute()->fetchColumn();
$pageID = sqlQueryBuilder()->select('id')->from('pages')
->where(Operator::equals(['id_block' => $rootId]))
->execute()->fetchColumn();
if ($pageID) {
$this->updateSQL('pages', ['updated' => date('Y-m-d H:i:s')], ['id' => $pageID]);
}
} else {
// save content - translations
$originalName = sqlQueryBuilder()->select('name')->from('blocks_translations')
->where(Operator::equals([
'id_block' => $data['id_block'],
'id_language' => $data['id_language'],
]))->setMaxResults(1)->execute()->fetchColumn(0);
if ($originalName === false) {
$this->insertSQL('blocks_translations',
[
'id_block' => $data['id_block'],
'id_language' => $data['id_language'],
'json_content' => $data['json_content'],
'content' => $data['content'],
]);
} else {
$this->updateSQL('blocks_translations',
[
'name' => $originalName,
'json_content' => $data['json_content'],
'content' => $data['content'],
'updated' => date('Y-m-d H:i:s'),
],
[
'id_block' => $data['id_block'],
'id_language' => $data['id_language'],
]);
}
}
// create photos relations
sqlQueryBuilder()->delete('photos_blocks_new_relation')
->where(Operator::equals(['id_block' => $data['id_block']]))
->execute();
if (!empty($imagesBlocks)) {
foreach ($imagesBlocks as $imageBlock) {
if (isset($imageBlock['settings']['photo'])) {
$imageBlock['settings']['photos'][0]['photo'] = $imageBlock['settings']['photo'];
}
$photos = $imageBlock['settings']['photos'];
foreach ($photos as $ph) {
try {
$photoId = $ph['photo']['id'];
sqlQueryBuilder()
->insert('photos_blocks_new_relation')
->directValues(
[
'id_photo' => $photoId,
'id_block' => $data['id_block'],
]
)->execute();
} catch (\Exception $e) {
}
}
}
}
} else {
throw new \Exception('Nor id_block, object_type or object_id specified!');
}
}
return true;
}
);
} catch (\Exception $e) {
$sentryLogger->captureException($e);
return new JsonResponse([
'result' => false,
'error' => $e->getMessage(),
]);
}
return new JsonResponse([
'result' => $result,
]);
}
protected function getBlockId(Block $blockUtil, ?int $id_block, ?string $objectType, ?int $objectId)
{
if ($id_block ?? false) {
return $id_block;
}
if (!$objectType || !$objectId) {
return false;
}
return $blockUtil->insertFirstBlock($objectType, $objectId, '');
}
public function getImageBlocks($settings)
{
$result = [];
foreach ($settings as $setting) {
$imageBlocks = array_filter(
$settings,
function ($x) {
return isset($x['settings']['photo']) || isset($x['settings']['photos']);
}
);
$result = array_merge($result, $imageBlocks);
if (!empty($setting['children'])) {
$result = array_merge($result, $this->getImageBlocks($setting['children']));
}
}
return $result;
}
public function doCheckPermissions()
{
if (!getAdminUser()) {
throw new NotFoundHttpException('Platnost přihlášení vypršela');
}
}
/**
* @Route("/_blocek/ProductsBlock/")
*/
public function getProductsAction(Request $request, Block $blockUtil)
{
if (!$request->getContent()) {
throw new NotFoundHttpException('Empty data');
}
$blocekData = json_decode_strict($request->getContent(), true);
$filterSpecs = $blockUtil->getProductsBlockSpecs($blocekData);
$smarty = createSmarty(false, true);
$smarty->assign('spec', $filterSpecs);
$smarty->assign('data', $blocekData);
$response['html'] = $smarty->fetch('block.products.blocek.tpl');
$response['result'] = true;
$response['filter'] = $blocekData;
// Detect empty product list in edit mode
if ($request->get('editor') && strpos($response['html'], 'href') === false) {
$response['result'] = false;
}
return new JsonResponse($response);
}
protected function getOrderDir($order_dir)
{
$order_dir = trim($order_dir);
if (($order_dir != 'ASC') && ($order_dir != 'DESC')) {
$order_dir = 'ASC';
}
return $order_dir;
}
protected function getOrderFields($order_by)
{
switch ($order_by) {
case 'code':
$order = 'p.code';
break;
case 'title':
$order = 'p.title';
break;
case 'price':
$order = 'p.price';
break;
case 'date':
$order = 'p.date_added';
break;
case 'sell':
$order = 'p.pieces_sold';
break;
case 'discount':
$order = 'p.discount';
break;
case 'store':
$order = 'p.in_store';
break;
case 'random':
$order = 'RAND()';
break;
default:
$order = 'p.title';
break;
}
return $order;
}
#[Route('/_blocek/settings/')]
public function getBlocekSettings(BlocekSettings $blocekSettings): JsonResponse
{
$blocekSettings->fetchSettings();
return new JsonResponse(['message' => 'BlocekSettings', 'data' => $blocekSettings->asArray()]);
}
/**
* @Route("/_blocek/templates/")
*/
public function getTemplatesAction(Block $blockUtils)
{
$message = null;
$data = [];
if (!findModule(\Modules::TEMPLATES)) {
return new JsonResponse(
[
'message' => 'No templates module',
'data' => $data,
]
);
}
$qb = sqlQueryBuilder()
->select('tc.*')
->from('templates_categories', 'tc')
->Where(Operator::like([JsonOperator::value('tc.data', 'show_in_editor') => 'Y']));
$templateCategories = sqlFetchAll($qb->execute(), 'id');
$templateCategoriesIDs = array_keys($templateCategories);
$templates = sqlQueryBuilder()
->select('t.*')
->from('templates', 't')
->where(Operator::inIntArray($templateCategoriesIDs, 't.id_category'));
foreach ($templates->execute() as $template) {
$blocks = $blockUtils->getBlocks($template['id_block']);
// beru obsah jen prvního bloku
if ($blocks[0]['content'] ?? false) {
$data[] = [
'name' => $template['name'],
'group' => $templateCategories[$template['id_category']]['name'],
'content' => json_decode($blocks[0]['json_content']),
];
}
}
return new JsonResponse(
[
'message' => $message,
'data' => $data,
]
);
}
#[Route('/_blocek/icons/')]
public function getIconsAction(Block $blockUtils): JsonResponse
{
$fontIcons = json_decode_strict(file_get_contents('web/build/icons-list.json'), true);
$fileIcons = new \GlobIterator('data/icons/*.svg', \FilesystemIterator::CURRENT_AS_PATHNAME);
$fileIcons = Mapping::mapKeys($fileIcons, fn ($fileName) => [basename('file-'.$fileName, '.svg'), '/'.$fileName]);
return new JsonResponse(
[
'success' => true,
'font_icons' => $fontIcons,
'file_icons' => $fileIcons,
]
);
}
#[Route('/_blocek/icons-inline/')]
public function getIconsInlineAction(Block $blockUtils): JsonResponse
{
if (!findModule(\Modules::COMPONENTS)) {
$fontIcons = json_decode_strict(file_get_contents('web/build/icons-list.json'), true);
}
$engineIcons = new \GlobIterator('engine/web/common/static/images/icons/*.svg', \FilesystemIterator::CURRENT_AS_PATHNAME);
$shopIcons = new \GlobIterator('data/icons/*.svg', \FilesystemIterator::CURRENT_AS_PATHNAME);
$fileIcons = [];
// Apparently, its not necessary to strip doctype and xml tag, blocek lib will handle this.
// foreach ($engineIcons as $iconPath) {
// $icon = Icon::fromFile($iconPath);
// $fileIcons[basename($iconPath, '.svg')] = $icon->toHtml();
// }
// foreach ($shopIcons as $iconPath) {
// $icon = Icon::fromFile($iconPath);
// $fileIcons[basename($iconPath, '.svg')] = $icon->toHtml();
// }
foreach ($engineIcons as $iconPath) {
$fileIcons[basename($iconPath, '.svg')] = file_get_contents($iconPath);
}
foreach ($shopIcons as $iconPath) {
$fileIcons[basename($iconPath, '.svg')] = file_get_contents($iconPath);
}
return new JsonResponse(
[
'success' => true,
'font_icons' => $fontIcons ?? [],
'file_icons' => $fileIcons,
]
);
}
}