<?php
namespace App\Controller;
use App\Controller\Catalog\CatalogRepository;
use Doctrine\DBAL\Connection;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class PageController extends BaseController
{
/**
* @Route("/", name="home", methods={"GET"})
*/
public function home(Request $request, Connection $db, CatalogRepository $catalog): Response
{
$products = $catalog->listProducts(15);
return $this->render('index.html.twig', [
'user' => $this->publicUser($this->getAuthorizedUser($request, $db)),
'catalog_products' => $products,
'catalog_payload_json' => $catalog->payloadJson($products),
]);
}
/**
* @Route("/profile", name="profile", methods={"GET"})
*/
public function profile(Request $request, Connection $db, CatalogRepository $catalog): Response
{
$user = $this->getAuthorizedUser($request, $db);
if (!$user) {
return $this->redirectToRoute('home', [
'auth' => 'login',
'redirect' => $request->getRequestUri(),
]);
}
$catalog->ensureSchema();
$orders = $this->profileOrders($db, $user);
return $this->render('profile.html.twig', [
'user' => $this->publicUser($user),
'profile_orders' => $orders,
'profile_orders_count' => count($orders),
'profile_purchase_groups' => $this->profilePurchaseGroups($orders, 5),
]);
}
/**
* @Route("/referral", name="referral", methods={"GET"})
*/
public function referral(Request $request, Connection $db): Response
{
$user = $this->getAuthorizedUser($request, $db);
if (!$user) {
return $this->redirectToRoute('home', [
'auth' => 'login',
'redirect' => $request->getRequestUri(),
]);
}
return $this->render('referral.html.twig', [
'user' => $this->publicUser($user),
'referral_url' => $this->referralUrl($request, $user),
]);
}
/**
* @Route("/topup", name="topup", methods={"GET"})
*/
public function topup(Request $request, Connection $db): Response
{
return $this->render('topup.html.twig', [
'user' => $this->publicUser($this->getAuthorizedUser($request, $db)),
]);
}
private function referralUrl(Request $request, array $user): string
{
return $request->getSchemeAndHttpHost() . '/?ref=' . rawurlencode((string) $user['id']);
}
private function profileOrders(Connection $db, array $user): array
{
$userId = (int) ($user['id'] ?? 0);
$email = trim((string) ($user['email'] ?? ''));
$where = [];
$params = [];
if ($userId > 0) {
$where[] = 'o.user_id = ?';
$params[] = $userId;
}
if ($email !== '') {
$where[] = 'LOWER(o.buyer_email) = LOWER(?)';
$params[] = $email;
}
if (!$where) {
return [];
}
try {
$rows = $db->fetchAllAssociative(
'SELECT o.*,
p.image_url AS product_image_url,
p.slug AS product_slug,
cp.public_id AS payment_public_id,
cp.pay_url AS payment_pay_url,
cp.status AS payment_status,
COALESCE(o.amount, cp.amount) AS display_amount,
COALESCE(o.currency, cp.currency) AS display_currency
FROM catalog_orders o
LEFT JOIN catalog_products p ON p.id = o.product_id
LEFT JOIN catalog_payments cp ON cp.id = (
SELECT cp2.id
FROM catalog_payments cp2
WHERE cp2.order_public_id = o.public_id
ORDER BY cp2.id DESC
LIMIT 1
)
WHERE (' . implode(' OR ', $where) . ')
ORDER BY o.created_at DESC, o.id DESC
LIMIT 200',
$params
);
} catch (\Throwable $error) {
return [];
}
return array_map(function (array $row): array {
return $this->profileOrderView($row);
}, $rows);
}
private function profilePurchaseGroups(array $orders, int $limit): array
{
$groups = [];
foreach (array_slice($orders, 0, $limit) as $order) {
$label = (string) ($order['date_group'] ?? '');
if ($label === '') {
$label = 'Без даты';
}
if (!isset($groups[$label])) {
$groups[$label] = [
'label' => $label,
'orders' => [],
];
}
$groups[$label]['orders'][] = $order;
}
return array_values($groups);
}
private function profileOrderView(array $row): array
{
$date = $this->profileOrderDate($row['created_at'] ?? null);
$status = strtolower(trim((string) ($row['status'] ?? 'created')));
$productSlug = trim((string) ($row['product_slug'] ?? ''));
$paymentPublicId = trim((string) ($row['payment_public_id'] ?? ''));
$paymentPayUrl = trim((string) ($row['payment_pay_url'] ?? ''));
$productUrl = $productSlug !== '' ? '/product?slug=' . rawurlencode($productSlug) : '#';
$statusClass = $this->profileOrderStatusClass($status);
return [
'id' => (string) ($row['public_id'] ?? ''),
'title' => (string) ($row['product_name'] ?? 'Товар PlayPapa'),
'date_iso' => $date->format('Y-m-d\TH:i:sP'),
'date_label' => $this->profileDateLabel($date),
'date_table_label' => $date->format('d.m.Y'),
'time_label' => $date->format('H:i'),
'date_group' => $this->profileDateGroup($date),
'amount_label' => $this->profileAmountLabel($row['display_amount'] ?? $row['amount'] ?? null, (string) ($row['display_currency'] ?? $row['currency'] ?? 'RUB')),
'status_label' => $this->profileOrderStatusLabel($status),
'status_class' => $statusClass,
'image' => $this->profileOrderImage($row['product_image_url'] ?? ''),
'product_url' => $productUrl,
'details_url' => $paymentPublicId !== '' ? '/pay/' . rawurlencode($paymentPublicId) : $productUrl,
'pay_url' => $paymentPayUrl,
'payable' => $paymentPayUrl !== '' && in_array($status, ['created', 'awaiting_payment', 'payment_pending', 'pending'], true),
'completed' => $statusClass === 'history-table__status--success',
];
}
private function profileOrderDate($value): \DateTimeImmutable
{
try {
return new \DateTimeImmutable((string) $value);
} catch (\Throwable $error) {
return new \DateTimeImmutable();
}
}
private function profileDateGroup(\DateTimeImmutable $date): string
{
$day = $date->setTime(0, 0);
$today = (new \DateTimeImmutable('today'))->setTime(0, 0);
$yesterday = $today->modify('-1 day');
if ($day->getTimestamp() === $today->getTimestamp()) {
return 'Сегодня';
}
if ($day->getTimestamp() === $yesterday->getTimestamp()) {
return 'Вчера';
}
return $date->format('d.m.Y');
}
private function profileDateLabel(\DateTimeImmutable $date): string
{
$months = [
1 => 'января',
2 => 'февраля',
3 => 'марта',
4 => 'апреля',
5 => 'мая',
6 => 'июня',
7 => 'июля',
8 => 'августа',
9 => 'сентября',
10 => 'октября',
11 => 'ноября',
12 => 'декабря',
];
return (int) $date->format('j') . ' ' . $months[(int) $date->format('n')] . ' ' . $date->format('Y');
}
private function profileAmountLabel($amount, string $currency): string
{
if ($amount === null || $amount === '') {
return '—';
}
$value = (float) $amount;
$precision = abs($value - round($value)) < 0.005 ? 0 : 2;
$currency = strtoupper(trim($currency ?: 'RUB'));
$symbols = [
'RUB' => '₽',
'USD' => '$',
'EUR' => '€',
'KZT' => '₸',
'UAH' => '₴',
];
return number_format($value, $precision, ',', ' ') . ' ' . ($symbols[$currency] ?? $currency);
}
private function profileOrderStatusLabel(string $status): string
{
$labels = [
'created' => 'Создан',
'awaiting_payment' => 'Ожидает оплату',
'payment_pending' => 'Ожидает оплату',
'payment_paid' => 'Оплата получена',
'sending' => 'В обработке',
'pending' => 'В обработке',
'completed' => 'Успешно',
'partial' => 'Частично',
'paid' => 'Успешно',
'failed' => 'Ошибка',
'galaxylink_error' => 'Ошибка',
'payment_failed' => 'Ошибка оплаты',
'expired' => 'Истёк',
'payment_expired' => 'Истёк',
'cancelled' => 'Отменён',
'payment_cancelled' => 'Отменён',
'refunded' => 'Возврат',
'manual_check' => 'Проверка',
];
return $labels[$status] ?? $status;
}
private function profileOrderStatusClass(string $status): string
{
if (in_array($status, ['completed', 'partial', 'paid'], true)) {
return 'history-table__status--success';
}
if (in_array($status, ['failed', 'galaxylink_error', 'payment_failed', 'expired', 'payment_expired', 'cancelled', 'payment_cancelled', 'refunded'], true)) {
return 'history-table__status--cancel';
}
return 'history-table__status--pending';
}
private function profileOrderImage($value): string
{
$image = trim((string) $value);
if ($image === '') {
return '/assets/product-doom.jpg';
}
if (preg_match('~^(?:https?:)?//~i', $image) || strpos($image, '/') === 0) {
return $image;
}
return '/' . ltrim($image, '/');
}
}