доставка в плавающем
This commit is contained in:
@@ -45,6 +45,56 @@ final class CatalogController
|
||||
]);
|
||||
}
|
||||
|
||||
public function promo(): void
|
||||
{
|
||||
$categoryRepository = new CategoryRepository();
|
||||
$productRepository = new ProductRepository();
|
||||
$pagination = $this->paginationInput();
|
||||
$listing = $productRepository->promoListing($pagination['limit'], $pagination['offset']);
|
||||
|
||||
view('catalog/index', [
|
||||
'title' => 'Акции Рыбсток - продукты по стоковым ценам с доставкой',
|
||||
'metaDescription' => 'Акционные товары Рыбсток: рыба, морепродукты, мясо, сыры, икра, полуфабрикаты и бакалея по стоковым ценам с доставкой по Москве и Московской области.',
|
||||
'metaKeywords' => 'акции Рыбсток, скидки на рыбу, продукты по акции Москва, рыба морепродукты мясо сыры по стоковым ценам',
|
||||
'breadcrumbs' => [
|
||||
['title' => 'Главная', 'url' => '/'],
|
||||
['title' => 'Акции'],
|
||||
],
|
||||
'categories' => $categoryRepository->tree(),
|
||||
'currentCategory' => null,
|
||||
'catalogHeading' => 'Акции',
|
||||
'catalogEyebrow' => 'Подборка',
|
||||
'catalogLead' => 'Товары с акционной витриной и перечеркнутой ценой. Итоговая цена в корзине дополнительно зависит от выбранного способа оплаты.',
|
||||
'products' => $listing['products'],
|
||||
'pagination' => $this->paginationData('/promo', $listing['total'], count($listing['products']), $pagination),
|
||||
]);
|
||||
}
|
||||
|
||||
public function newest(): void
|
||||
{
|
||||
$categoryRepository = new CategoryRepository();
|
||||
$productRepository = new ProductRepository();
|
||||
$pagination = $this->paginationInput();
|
||||
$listing = $productRepository->newListing($pagination['limit'], $pagination['offset']);
|
||||
|
||||
view('catalog/index', [
|
||||
'title' => 'Новинки Рыбсток - последние поступления на склад',
|
||||
'metaDescription' => 'Новые поступления Рыбсток: свежие позиции каталога, новые фасовки и товары, недавно добавленные на склад с доставкой по Москве и Московской области.',
|
||||
'metaKeywords' => 'новинки Рыбсток, новые поступления рыбы, свежие поступления морепродуктов, новые товары склад продуктов Москва',
|
||||
'breadcrumbs' => [
|
||||
['title' => 'Главная', 'url' => '/'],
|
||||
['title' => 'Новинки'],
|
||||
],
|
||||
'categories' => $categoryRepository->tree(),
|
||||
'currentCategory' => null,
|
||||
'catalogHeading' => 'Новинки',
|
||||
'catalogEyebrow' => 'Последние поступления',
|
||||
'catalogLead' => 'Последние добавленные позиции и товары, у которых изменилась фасовка. Новинки помогают быстро увидеть, что недавно появилось на складе.',
|
||||
'products' => $listing['products'],
|
||||
'pagination' => $this->paginationData('/new', $listing['total'], count($listing['products']), $pagination),
|
||||
]);
|
||||
}
|
||||
|
||||
public function search(string $query): void
|
||||
{
|
||||
$_GET['q'] = trim($query);
|
||||
|
||||
@@ -34,15 +34,15 @@ final class HomeController
|
||||
$homeCatalogSections[] = [
|
||||
'key' => 'promos',
|
||||
'title' => 'Акции',
|
||||
'url' => '/catalog',
|
||||
'count' => 10,
|
||||
'url' => '/promo',
|
||||
'count' => 50,
|
||||
'kind' => 'promo',
|
||||
'products' => $productRepository->promoPreview(10),
|
||||
];
|
||||
$homeCatalogSections[] = [
|
||||
'key' => 'new',
|
||||
'title' => 'Новинки',
|
||||
'url' => '/catalog',
|
||||
'url' => '/new',
|
||||
'count' => 50,
|
||||
'kind' => 'new',
|
||||
'products' => $productRepository->newPreview(10),
|
||||
|
||||
@@ -30,7 +30,15 @@ final class ProductRepository extends BaseRepository
|
||||
*/
|
||||
public function promoPreview(int $limit = 10): array
|
||||
{
|
||||
$limit = max(1, min($limit, 10));
|
||||
return array_slice($this->promoProducts(max(1, min($limit, 10))), 0, max(1, min($limit, 10)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
private function promoProducts(int $limit = 50): array
|
||||
{
|
||||
$limit = max(1, min($limit, 50));
|
||||
$products = $this->manualPromoPreview($limit);
|
||||
$usedIds = array_flip(array_map(static fn (array $product): int => (int) ($product['id'] ?? 0), $products));
|
||||
|
||||
@@ -63,6 +71,20 @@ final class ProductRepository extends BaseRepository
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{products: array<int, array<string, mixed>>, total: int}
|
||||
*/
|
||||
public function promoListing(int $limit = 48, int $offset = 0): array
|
||||
{
|
||||
$products = $this->promoProducts(50);
|
||||
$total = count($products);
|
||||
|
||||
return [
|
||||
'products' => array_slice($products, max(0, $offset), max(1, min($limit, 48))),
|
||||
'total' => $total,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
@@ -88,6 +110,20 @@ final class ProductRepository extends BaseRepository
|
||||
return $products;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{products: array<int, array<string, mixed>>, total: int}
|
||||
*/
|
||||
public function newListing(int $limit = 48, int $offset = 0): array
|
||||
{
|
||||
$products = $this->newPreview(50);
|
||||
$total = count($products);
|
||||
|
||||
return [
|
||||
'products' => array_slice($products, max(0, $offset), max(1, min($limit, 48))),
|
||||
'total' => $total,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
@@ -151,6 +187,23 @@ final class ProductRepository extends BaseRepository
|
||||
return (int) $statement->fetchColumn();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array{slug: string, updated_at: string|null, published_at: string|null, created_at: string|null}>
|
||||
*/
|
||||
public function sitemapProducts(int $limit = 10000): array
|
||||
{
|
||||
$statement = $this->pdo()->query(
|
||||
'SELECT slug, updated_at, published_at, created_at
|
||||
FROM products
|
||||
WHERE is_published = 1
|
||||
AND is_available = 1
|
||||
ORDER BY updated_at DESC, published_at DESC, created_at DESC, id DESC
|
||||
LIMIT ' . max(1, min($limit, 10000))
|
||||
);
|
||||
|
||||
return $statement->fetchAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{products: array<int, array<string, mixed>>, total: int}
|
||||
*/
|
||||
@@ -291,7 +344,17 @@ final class ProductRepository extends BaseRepository
|
||||
);
|
||||
$statement->execute(['product_id' => $productId]);
|
||||
|
||||
return $statement->fetchAll();
|
||||
return array_map(
|
||||
static function (array $image): array {
|
||||
$path = (string) ($image['path'] ?? '');
|
||||
$image['preview_path'] = \function_exists('optimized_image_path')
|
||||
? \optimized_image_path($path, 'preview')
|
||||
: $path;
|
||||
|
||||
return $image;
|
||||
},
|
||||
$statement->fetchAll()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -435,7 +498,9 @@ final class ProductRepository extends BaseRepository
|
||||
'variantId' => !empty($row['variant_id']) ? (int) $row['variant_id'] : $variantId,
|
||||
'name' => (string) ($row['name'] ?? ''),
|
||||
'slug' => (string) ($row['slug'] ?? ''),
|
||||
'image' => (string) ($row['main_image_path'] ?? ''),
|
||||
'image' => \function_exists('optimized_image_path')
|
||||
? \optimized_image_path((string) ($row['main_image_path'] ?? ''), 'preview')
|
||||
: (string) ($row['main_image_path'] ?? ''),
|
||||
'variantName' => (string) ($row['variant_name'] ?? 'Фасовка'),
|
||||
'unit' => (string) ($row['unit'] ?? ''),
|
||||
'packageQuantity' => (float) ($row['package_quantity'] ?? 0),
|
||||
|
||||
@@ -36,6 +36,44 @@ function versioned_asset(string $path): string
|
||||
return $assetPath . $querySeparator . filemtime($publicPath);
|
||||
}
|
||||
|
||||
function public_asset_exists(string $path): bool
|
||||
{
|
||||
$assetPath = '/' . ltrim(strtok($path, '?') ?: $path, '/');
|
||||
|
||||
return is_file(base_path('public' . $assetPath));
|
||||
}
|
||||
|
||||
function optimized_image_path(?string $path, string $mode = 'preview'): string
|
||||
{
|
||||
$assetPath = trim((string) $path);
|
||||
if ($assetPath === '' || !str_starts_with($assetPath, '/assets/uploads/old/products/')) {
|
||||
return $assetPath;
|
||||
}
|
||||
|
||||
$assetPath = '/' . ltrim(strtok($assetPath, '?') ?: $assetPath, '/');
|
||||
$directory = rtrim(str_replace('\\', '/', dirname($assetPath)), '/');
|
||||
$filename = basename($assetPath);
|
||||
$isFull = str_starts_with($filename, 'full_');
|
||||
$baseFilename = $isFull ? substr($filename, 5) : $filename;
|
||||
|
||||
$candidates = $mode === 'detail'
|
||||
? [$baseFilename, 'thumb_' . $baseFilename]
|
||||
: ['thumb_' . $baseFilename, $baseFilename];
|
||||
|
||||
foreach ($candidates as $candidate) {
|
||||
if ($candidate === '' || $candidate === $filename) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$candidatePath = $directory . '/' . $candidate;
|
||||
if (public_asset_exists($candidatePath)) {
|
||||
return $candidatePath;
|
||||
}
|
||||
}
|
||||
|
||||
return $assetPath;
|
||||
}
|
||||
|
||||
function view(string $template, array $data = []): void
|
||||
{
|
||||
View::render($template, $data);
|
||||
|
||||
Reference in New Issue
Block a user