feat: added new products_carousel bock type

This commit is contained in:
2025-11-13 13:41:35 +03:00
parent 6f9855995d
commit f0837e5c94
22 changed files with 747 additions and 108 deletions

View File

@@ -4,6 +4,8 @@ use Bastion\ApplicationFactory;
use Cart\User;
use Openguru\OpenCartFramework\Application;
use Openguru\OpenCartFramework\Http\Response as HttpResponse;
use Openguru\OpenCartFramework\ImageTool\ImageTool;
use Openguru\OpenCartFramework\ImageTool\ImageToolInterface;
use Openguru\OpenCartFramework\Logger\LoggerInterface;
use Openguru\OpenCartFramework\Logger\OpenCartLogAdapter;
use Openguru\OpenCartFramework\OpenCart\Decorators\OcRegistryDecorator;
@@ -127,11 +129,12 @@ class ControllerExtensionModuleTgshop extends Controller
->createApplication()
->bootAndHandleRequest();
} catch (Exception $e) {
$this->log->write('[TELECART] Error: ' . $e->getMessage());
$logger = new OpenCartLogAdapter($this->log, 'TeleCart');
$logger->logException($e);
http_response_code(HttpResponse::HTTP_INTERNAL_SERVER_ERROR);
header('Content-Type: application/json');
echo json_encode([
'error' => 'Ошибка сервера. Приносим свои извинения за неудобства.',
'error' => 'Server Error.',
], JSON_THROW_ON_ERROR);
}
}
@@ -272,6 +275,7 @@ class ControllerExtensionModuleTgshop extends Controller
$app = ApplicationFactory::create($items);
$app->bind(OcRegistryDecorator::class, fn() => new OcRegistryDecorator($this->registry));
$app->bind(ImageToolInterface::class, fn() => new ImageTool(DIR_IMAGE, HTTPS_SERVER));
$app
->withLogger(fn() => new OpenCartLogAdapter(

View File

@@ -2,18 +2,51 @@
namespace Bastion\Handlers;
use App\Services\SettingsService;
use Openguru\OpenCartFramework\Http\JsonResponse;
use Openguru\OpenCartFramework\Http\Request;
use Openguru\OpenCartFramework\OpenCart\Decorators\OcRegistryDecorator;
use Openguru\OpenCartFramework\QueryBuilder\Builder;
use Openguru\OpenCartFramework\QueryBuilder\JoinClause;
use Openguru\OpenCartFramework\Support\Str;
class AutocompleteHandler
{
private OcRegistryDecorator $registry;
private Builder $queryBuilder;
private SettingsService $settings;
public function __construct(OcRegistryDecorator $registry)
{
public function __construct(
OcRegistryDecorator $registry,
Builder $queryBuilder,
SettingsService $settings
) {
$this->registry = $registry;
$this->queryBuilder = $queryBuilder;
$this->settings = $settings;
}
public function getCategoriesFlat(): JsonResponse
{
$languageId = $this->settings->config()->getApp()->getLanguageId();
$categoriesFlat = $this->getFlatCategories($languageId);
return new JsonResponse([
'data' => $categoriesFlat,
]);
}
public function getCategories(): JsonResponse
{
$languageId = $this->settings->config()->getApp()->getLanguageId();
$categoriesFlat = $this->getFlatCategories($languageId);
$categories = $this->buildCategoryTree($categoriesFlat);
return new JsonResponse([
'data' => $categories,
]);
}
public function getProductsById(Request $request): JsonResponse
@@ -61,4 +94,53 @@ class AutocompleteHandler
'data' => $items,
]);
}
private function getFlatCategories(int $languageId): array
{
return $this->queryBuilder->newQuery()
->select([
'categories.category_id' => 'id',
'categories.parent_id' => 'parent_id',
'descriptions.name' => 'name',
'descriptions.description' => 'description',
])
->from(db_table('category'), 'categories')
->join(
db_table('category_description') . ' AS descriptions',
function (JoinClause $join) use ($languageId) {
$join->on('categories.category_id', '=', 'descriptions.category_id')
->where('descriptions.language_id', '=', $languageId);
}
)
->where('categories.status', '=', 1)
->orderBy('parent_id')
->orderBy('sort_order')
->get();
}
private function buildCategoryTree(array $flat, $parentId = 0): array
{
$branch = [];
foreach ($flat as $category) {
if ((int) $category['parent_id'] === (int) $parentId) {
$children = $this->buildCategoryTree($flat, $category['id']);
if ($children) {
$category['children'] = $children;
}
$branch[] = [
'key' => (int) $category['id'],
'label' => Str::htmlEntityEncode($category['name']),
'data' => [
'description' => Str::htmlEntityEncode($category['description']),
],
'icon' => null,
'children' => $category['children'] ?? [],
];
}
}
return $branch;
}
}

View File

@@ -0,0 +1,58 @@
<?php
namespace Bastion\Handlers;
use App\Services\SettingsService;
use Openguru\OpenCartFramework\Http\JsonResponse;
use Openguru\OpenCartFramework\Http\Request;
use Openguru\OpenCartFramework\ImageTool\ImageToolInterface;
use Openguru\OpenCartFramework\QueryBuilder\Builder;
use Openguru\OpenCartFramework\QueryBuilder\JoinClause;
class DictionariesHandler
{
private Builder $queryBuilder;
private ImageToolInterface $ocImageTool;
private SettingsService $settings;
public function __construct(Builder $queryBuilder, ImageToolInterface $ocImageTool, SettingsService $settings)
{
$this->queryBuilder = $queryBuilder;
$this->ocImageTool = $ocImageTool;
$this->settings = $settings;
}
public function getCategories(Request $request): JsonResponse
{
$perPage = $request->get('perPage', 20);
$categoryIds = $request->json('category_ids', []);
$languageId = $this->settings->config()->getApp()->getLanguageId();
$data = $this->queryBuilder->newQuery()
->select([
'categories.category_id' => 'id',
'categories.parent_id' => 'parent_id',
'categories.image' => 'image',
'descriptions.name' => 'name',
'descriptions.description' => 'description',
])
->from(db_table('category'), 'categories')
->join(
db_table('category_description') . ' AS descriptions',
function (JoinClause $join) use ($languageId) {
$join->on('categories.category_id', '=', 'descriptions.category_id')
->where('descriptions.language_id', '=', $languageId);
}
)
->where('categories.status', '=', 1)
->when($categoryIds, function (Builder $query) use ($categoryIds) {
$query->whereIn('categories.category_id', $categoryIds);
})
->orderBy('parent_id')
->orderBy('sort_order')
->limit($perPage)
->get();
return new JsonResponse(compact('data'));
}
}

View File

@@ -1,6 +1,7 @@
<?php
use Bastion\Handlers\AutocompleteHandler;
use Bastion\Handlers\DictionariesHandler;
use Bastion\Handlers\SettingsHandler;
use Bastion\Handlers\StatsHandler;
use Bastion\Handlers\TelegramHandler;
@@ -15,4 +16,9 @@ return [
'getCategoriesById' => [AutocompleteHandler::class, 'getCategoriesById'],
'getDashboardStats' => [StatsHandler::class, 'getDashboardStats'],
'tgGetMe' => [TelegramHandler::class, 'tgGetMe'],
'getCategories' => [DictionariesHandler::class, 'getCategories'],
'getAutocompleteCategories' => [AutocompleteHandler::class, 'getCategories'],
'getAutocompleteCategoriesFlat' => [AutocompleteHandler::class, 'getCategoriesFlat'],
];

View File

@@ -61,7 +61,7 @@ class CategoriesHandler
return new JsonResponse([
'data' => array_map(static function ($category) {
return [
'id' => (int)$category['id'],
'id' => (int) $category['id'],
'image' => $category['image'] ?? '',
'name' => Utils::htmlEntityEncode($category['name']),
'description' => $category['description'],
@@ -71,20 +71,26 @@ class CategoriesHandler
]);
}
public function buildCategoryTree(array $flat, $parentId = 0): array {
public function buildCategoryTree(array $flat, $parentId = 0): array
{
$branch = [];
foreach ($flat as $category) {
if ((int)$category['parent_id'] === (int)$parentId) {
if ((int) $category['parent_id'] === (int) $parentId) {
$children = $this->buildCategoryTree($flat, $category['id']);
if ($children) {
$category['children'] = $children;
}
$image = $this->ocImageTool->resize($category['image'] ?? '', self::THUMB_SIZE, self::THUMB_SIZE, 'no_image.png');
$image = $this->ocImageTool->resize(
$category['image'] ?? '',
self::THUMB_SIZE,
self::THUMB_SIZE,
'no_image.png'
);
$branch[] = [
'id' => (int)$category['id'],
'id' => (int) $category['id'],
'image' => $image,
'name' => Utils::htmlEntityEncode($category['name']),
'description' => $category['description'],

View File

@@ -15,6 +15,7 @@ class BlocksService
'slider' => [self::class, 'processSlider'],
'categories_top' => [self::class, 'processCategoriesTop'],
'products_feed' => [self::class, 'processProductsFeed'],
'products_carousel' => [self::class, 'processProductsCarousel'],
];
private LoggerInterface $logger;
@@ -22,25 +23,28 @@ class BlocksService
private CacheInterface $cache;
private SettingsService $settings;
private Builder $queryBuilder;
private ProductsService $productsService;
public function __construct(
LoggerInterface $logger,
ImageToolInterface $imageTool,
CacheInterface $cache,
SettingsService $settings,
Builder $queryBuilder
Builder $queryBuilder,
ProductsService $productsService
) {
$this->logger = $logger;
$this->imageTool = $imageTool;
$this->cache = $cache;
$this->settings = $settings;
$this->queryBuilder = $queryBuilder;
$this->productsService = $productsService;
}
public function process(array $block): array
{
$blockType = $block['type'];
$cacheKey = "block_$blockType";
$cacheKey = "block_{$blockType}_" . md5(serialize($block['data']));
$cacheTtlSeconds = 60;
$data = $this->cache->get($cacheKey);
@@ -118,4 +122,36 @@ class BlocksService
{
return $block;
}
private function processProductsCarousel(array $block): array
{
$categoryId = $block['data']['category_id'];
$languageId = $this->settings->config()->getApp()->getLanguageId();
$params = [
'page' => 1,
'perPage' => 10,
'filters' => [
"operand" => "AND",
"rules" => [
"RULE_PRODUCT_CATEGORIES" => [
"criteria" => [
"product_category_ids" => [
"type" => "product_categories",
"params" => [
"operator" => "contains",
"value" => [$categoryId],
],
],
],
],
],
],
];
$response = $this->productsService->getProductsResponse($params, $languageId);
$block['data']['products'] = $response;
return $block;
}
}

View File

@@ -53,7 +53,7 @@ class ProductsService
{
$page = $params['page'];
$perPage = $params['perPage'];
$search = $params['search'];
$search = $params['search'] ?? false;
$categoryName = '';
$imageWidth = 300;
$imageHeight = 300;