шаг 5 завершен
This commit is contained in:
@@ -15,9 +15,9 @@ final class CatalogController
|
||||
$productRepository = new ProductRepository();
|
||||
|
||||
view('catalog/index', [
|
||||
'title' => 'Каталог продукции - Рыбсток',
|
||||
'metaDescription' => 'Полный каталог Рыбсток: рыба, морепродукты, икра, мясо, сыры, полуфабрикаты и продукты с доставкой.',
|
||||
'metaKeywords' => 'каталог Рыбсток, рыба, морепродукты, мясо, сыры, полуфабрикаты',
|
||||
'title' => 'Каталог Рыбсток - рыба, морепродукты, мясо, сыры и продукты',
|
||||
'metaDescription' => 'Полный каталог Рыбсток: рыба, морепродукты, икра, мясо, сыры, полуфабрикаты, овощи, ягоды и бакалея по стоковым ценам с доставкой по Москве и Московской области.',
|
||||
'metaKeywords' => 'каталог Рыбсток, купить рыбу Москва, морепродукты Москва, мясо доставка Москва, сыры доставка, икра, полуфабрикаты, овощи ягоды заморозка, бакалея, продукты по стоковым ценам',
|
||||
'breadcrumbs' => [
|
||||
['title' => 'Главная', 'url' => '/'],
|
||||
['title' => 'Каталог'],
|
||||
@@ -47,10 +47,12 @@ final class CatalogController
|
||||
return;
|
||||
}
|
||||
|
||||
$categoryName = (string) $category['name'];
|
||||
|
||||
view('catalog/index', [
|
||||
'title' => ($category['seo_title'] ?: $category['name']) . ' - Рыбсток',
|
||||
'metaDescription' => $category['seo_description'] ?: '',
|
||||
'metaKeywords' => $category['seo_keywords'] ?: '',
|
||||
'title' => $category['seo_title'] ?: $categoryName . ' - купить в Рыбсток с доставкой по Москве',
|
||||
'metaDescription' => $category['seo_description'] ?: $categoryName . ' в интернет-складе Рыбсток: качественная продукция по стоковым ценам, фасовки для дома и бизнеса, доставка по Москве и Московской области.',
|
||||
'metaKeywords' => $category['seo_keywords'] ?: $categoryName . ', купить ' . $categoryName . ' Москва, ' . $categoryName . ' доставка, Рыбсток, продукты по стоковым ценам',
|
||||
'breadcrumbs' => $categoryRepository->breadcrumbs((int) $category['id']),
|
||||
'categories' => $categoryRepository->tree(),
|
||||
'currentCategory' => $category,
|
||||
|
||||
@@ -38,9 +38,9 @@ final class HomeController
|
||||
}
|
||||
|
||||
view('pages/home', [
|
||||
'title' => 'Рыбсток - продукты и рыба с доставкой по Москве и Московской области',
|
||||
'metaDescription' => 'Рыбсток - интернет-магазин рыбы, морепродуктов, мяса, сыров, полуфабрикатов и продуктов с доставкой по Москве и Московской области.',
|
||||
'metaKeywords' => 'Рыбсток, рыба, морепродукты, мясо, сыры, полуфабрикаты, доставка продуктов Москва',
|
||||
'title' => 'Рыбсток - качественные продукты по стоковым ценам с доставкой',
|
||||
'metaDescription' => 'Рыбсток - интернет-склад качественной рыбы, морепродуктов, мяса, сыров, овощей, ягод и бакалеи по стоковым ценам. Бесплатная доставка по Москве и Московской области, самовывоз с ул. Привольная.',
|
||||
'metaKeywords' => 'Рыбсток, рыба доставка Москва, морепродукты доставка Москва, мясо доставка Москва, сыры доставка, икра доставка Москва, овощи ягоды заморозка, бакалея с доставкой, продукты по стоковым ценам, интернет склад продуктов, бесплатная доставка продуктов Москва, доставка продуктов Московская область',
|
||||
'breadcrumbs' => [
|
||||
['title' => 'Главная'],
|
||||
],
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
final class PageController
|
||||
{
|
||||
public function delivery(): void
|
||||
{
|
||||
view('pages/delivery', [
|
||||
'title' => 'Доставка Рыбсток - Москва и Московская область',
|
||||
'metaDescription' => 'Условия доставки Рыбсток по Москве и Московской области: бесплатная доставка внутри МКАД от 5000 рублей, за МКАД от 10000 рублей, самовывоз и расчет доставки.',
|
||||
'metaKeywords' => 'Рыбсток доставка, доставка рыбы Москва, доставка продуктов Москва, доставка за МКАД, самовывоз Рыбсток, расчет доставки',
|
||||
'breadcrumbs' => [
|
||||
['title' => 'Главная', 'url' => '/'],
|
||||
['title' => 'Доставка'],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function payment(): void
|
||||
{
|
||||
view('pages/payment', [
|
||||
'title' => 'Оплата заказа - Рыбсток',
|
||||
'metaDescription' => 'Способы оплаты заказа в Рыбсток: оплата при получении наличными или QR-кодом, безналичный расчет от 30000 рублей.',
|
||||
'metaKeywords' => 'Рыбсток оплата, оплата при получении, оплата QR кодом, безналичный расчет продукты',
|
||||
'breadcrumbs' => [
|
||||
['title' => 'Главная', 'url' => '/'],
|
||||
['title' => 'Оплата'],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function contacts(): void
|
||||
{
|
||||
view('pages/contacts', [
|
||||
'title' => 'Контакты Рыбсток - телефон, адрес самовывоза, email',
|
||||
'metaDescription' => 'Контакты Рыбсток: телефоны для заказа и консультации, адрес пункта самовывоза в Москве на ул. Привольная, д. 13к1, email для заявок.',
|
||||
'metaKeywords' => 'Рыбсток контакты, телефон Рыбсток, адрес самовывоза Рыбсток, zakaz@rybstock.ru',
|
||||
'breadcrumbs' => [
|
||||
['title' => 'Главная', 'url' => '/'],
|
||||
['title' => 'Контакты'],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function returns(): void
|
||||
{
|
||||
view('pages/returns', [
|
||||
'title' => 'Правила возврата товара - Рыбсток',
|
||||
'metaDescription' => 'Правила возврата товара в интернет-складе Рыбсток: условия возврата, сохранность упаковки, температурный режим и порядок обращения.',
|
||||
'metaKeywords' => 'Рыбсток возврат товара, правила возврата товара, возврат продуктов, условия возврата Рыбсток',
|
||||
'breadcrumbs' => [
|
||||
['title' => 'Главная', 'url' => '/'],
|
||||
['title' => 'Правила возврата товара'],
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Core;
|
||||
|
||||
use App\Repositories\AdminRepository;
|
||||
|
||||
final class Auth
|
||||
{
|
||||
private const SESSION_ADMIN_ID = 'admin_id';
|
||||
|
||||
public static function start(): void
|
||||
{
|
||||
if (session_status() === PHP_SESSION_ACTIVE) {
|
||||
return;
|
||||
}
|
||||
|
||||
session_name('rybstock_admin');
|
||||
session_start();
|
||||
}
|
||||
|
||||
public static function requireAdmin(): void
|
||||
{
|
||||
self::start();
|
||||
|
||||
if (self::admin() !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$next = $_SERVER['REQUEST_URI'] ?? '/admin/';
|
||||
header('Location: /admin/login.php?next=' . rawurlencode($next));
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>|null
|
||||
*/
|
||||
public static function admin(): ?array
|
||||
{
|
||||
self::start();
|
||||
$adminId = (int) ($_SESSION[self::SESSION_ADMIN_ID] ?? 0);
|
||||
|
||||
if ($adminId <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$admin = (new AdminRepository())->findActiveById($adminId);
|
||||
|
||||
if ($admin === null) {
|
||||
unset($_SESSION[self::SESSION_ADMIN_ID]);
|
||||
}
|
||||
|
||||
return $admin;
|
||||
}
|
||||
|
||||
public static function attempt(string $email, string $password): bool
|
||||
{
|
||||
self::start();
|
||||
$repository = new AdminRepository();
|
||||
$admin = $repository->findActiveByEmail($email);
|
||||
|
||||
if ($admin === null || !password_verify($password, (string) $admin['password_hash'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
session_regenerate_id(true);
|
||||
$_SESSION[self::SESSION_ADMIN_ID] = (int) $admin['id'];
|
||||
$repository->touchLastLogin((int) $admin['id']);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function logout(): void
|
||||
{
|
||||
self::start();
|
||||
$_SESSION = [];
|
||||
|
||||
if (ini_get('session.use_cookies')) {
|
||||
$params = session_get_cookie_params();
|
||||
setcookie(session_name(), '', time() - 42000, $params['path'], $params['domain'], (bool) $params['secure'], (bool) $params['httponly']);
|
||||
}
|
||||
|
||||
session_destroy();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Repositories;
|
||||
|
||||
final class AdminRepository extends BaseRepository
|
||||
{
|
||||
public function activeCount(): int
|
||||
{
|
||||
return (int) $this->pdo()
|
||||
->query('SELECT COUNT(*) FROM admins WHERE is_active = 1')
|
||||
->fetchColumn();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>|null
|
||||
*/
|
||||
public function findActiveByEmail(string $email): ?array
|
||||
{
|
||||
$statement = $this->pdo()->prepare(
|
||||
'SELECT id, name, email, password_hash, role
|
||||
FROM admins
|
||||
WHERE email = :email AND is_active = 1
|
||||
LIMIT 1'
|
||||
);
|
||||
$statement->execute(['email' => mb_strtolower(trim($email))]);
|
||||
$admin = $statement->fetch();
|
||||
|
||||
return $admin === false ? null : $admin;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>|null
|
||||
*/
|
||||
public function findActiveById(int $id): ?array
|
||||
{
|
||||
$statement = $this->pdo()->prepare(
|
||||
'SELECT id, name, email, role
|
||||
FROM admins
|
||||
WHERE id = :id AND is_active = 1
|
||||
LIMIT 1'
|
||||
);
|
||||
$statement->execute(['id' => $id]);
|
||||
$admin = $statement->fetch();
|
||||
|
||||
return $admin === false ? null : $admin;
|
||||
}
|
||||
|
||||
public function createOwner(string $name, string $email, string $password): int
|
||||
{
|
||||
$name = trim($name);
|
||||
$email = mb_strtolower(trim($email));
|
||||
|
||||
if ($name === '' || $email === '' || $password === '') {
|
||||
throw new \InvalidArgumentException('Имя, email и пароль обязательны.');
|
||||
}
|
||||
|
||||
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
throw new \InvalidArgumentException('Укажите корректный email.');
|
||||
}
|
||||
|
||||
if (mb_strlen($password) < 8) {
|
||||
throw new \InvalidArgumentException('Пароль должен быть не короче 8 символов.');
|
||||
}
|
||||
|
||||
$statement = $this->pdo()->prepare(
|
||||
'INSERT INTO admins (name, email, password_hash, role, is_active)
|
||||
VALUES (:name, :email, :password_hash, "owner", 1)'
|
||||
);
|
||||
$statement->execute([
|
||||
'name' => $name,
|
||||
'email' => $email,
|
||||
'password_hash' => password_hash($password, PASSWORD_DEFAULT),
|
||||
]);
|
||||
|
||||
return (int) $this->pdo()->lastInsertId();
|
||||
}
|
||||
|
||||
public function touchLastLogin(int $id): void
|
||||
{
|
||||
$statement = $this->pdo()->prepare(
|
||||
'UPDATE admins SET last_login_at = NOW() WHERE id = :id'
|
||||
);
|
||||
$statement->execute(['id' => $id]);
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,15 @@ final class PriceTierService
|
||||
];
|
||||
}
|
||||
|
||||
$tiers[] = [
|
||||
'label' => 'Безналичный расчет',
|
||||
'code' => null,
|
||||
'min_order_amount' => 0.0,
|
||||
'discount_percent' => -8.0,
|
||||
'price' => round($basePrice * 1.08, 2),
|
||||
'note' => '+8% к базовой цене при безналичном расчете',
|
||||
];
|
||||
|
||||
return $tiers;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user