400 lines
19 KiB
PHP
400 lines
19 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace app\controllers;
|
||
|
||
use yii;
|
||
use yii\web\Controller;
|
||
use yii\web\Response;
|
||
|
||
use app\models\Account;
|
||
use app\models\Product;
|
||
use app\models\Supply;
|
||
use app\models\Search;
|
||
|
||
use app\models\connection\Dellin;
|
||
use app\models\Settings;
|
||
use Exception;
|
||
|
||
class SearchController extends Controller
|
||
{
|
||
/**
|
||
* @todo Сессию привязать к аккаунту и проверку по нему делать, иначе её можно просто сбрасывать
|
||
*/
|
||
public function actionIndex(): array|string
|
||
{
|
||
// Инициализация параметров
|
||
$auth_only = false;
|
||
|
||
if ($auth_only && yii::$app->user->isGuest) {
|
||
// Если активирован режим "Поиск только аутентифицированным" и запрос пришел не от аутентифицированного
|
||
|
||
// Запись кода ответа: 401 (необходима авторизация)
|
||
yii::$app->response->statusCode = 401;
|
||
|
||
// Переход к концу обработки
|
||
goto skip_search;
|
||
}
|
||
|
||
// Инициализация
|
||
$query = yii::$app->request->post('request') ?? yii::$app->request->get('q');
|
||
|
||
if (yii::$app->request->post('type') === 'product' || yii::$app->request->get('type') === 'product') {
|
||
// Поиск по продуктам
|
||
|
||
if (yii::$app->request->post('history')) {
|
||
// Запрошена история
|
||
|
||
yii::$app->response->format = Response::FORMAT_JSON;
|
||
|
||
return [
|
||
'panel' => $this->renderPartial('/search/panel', ['history' => true]),
|
||
'_csrf' => yii::$app->request->getCsrfToken()
|
||
];
|
||
}
|
||
|
||
// Инициализация сессии
|
||
$session = yii::$app->session;
|
||
|
||
// Инициализация ответа
|
||
$response = null;
|
||
|
||
// Инициализация параметров
|
||
$timer = 0;
|
||
|
||
// Период пропуска запросов (в секундах)
|
||
$period = 3;
|
||
$keep_connect = false;
|
||
$sanction = false;
|
||
$sanction_condition = ($session['last_request'] + $period - time()) < $period;
|
||
$sanction_time = 2;
|
||
$query_min = 2;
|
||
$query_max = 20;
|
||
|
||
if (isset($session['last_request'])) {
|
||
// Данные о времени последнего запроса не найдены
|
||
|
||
if ($sanction && $sanction_condition) {
|
||
// Наказание за повторный запрос при условии задержки
|
||
|
||
$session['last_request'] += $sanction_time;
|
||
}
|
||
} else {
|
||
// Это первый запрос
|
||
|
||
// Инициализация
|
||
$session['last_request'] = time();
|
||
|
||
// Пропуск проверок
|
||
goto first_request;
|
||
}
|
||
|
||
// Метка: "Повтор обработки поиска" (для создания непрерывного соединения)
|
||
keep_connect_wait:
|
||
|
||
// Запись времени последнего запроса и вычисление об истечении таймера
|
||
$timer = $session['last_request'] + $period - time();
|
||
|
||
if ($timer > 0) {
|
||
// Время блокировки не истекло
|
||
|
||
// Запись кода ответа: 202 (запрос не обработан)
|
||
yii::$app->response->statusCode = 202;
|
||
|
||
$return = [
|
||
'timer' => $timer,
|
||
'panel' => $this->renderPartial('/search/loading'),
|
||
'_csrf' => yii::$app->request->getCsrfToken()
|
||
];
|
||
} else {
|
||
// Запрос
|
||
|
||
// Очистка времени последнего запроса
|
||
unset($session['last_request']);
|
||
|
||
// Метка: "Первый запрос"
|
||
first_request:
|
||
|
||
if (strlen($query) < $query_min) {
|
||
// Выход за ограничения длины с недостатком
|
||
|
||
// Пропуск поиска
|
||
goto skip_query;
|
||
} else if (strlen($query) > $query_max) {
|
||
// Выход за ограничения длины с превышением
|
||
|
||
// Пропуск поиска
|
||
goto skip_query;
|
||
}
|
||
|
||
// Запись в историю
|
||
Search::write($query);
|
||
|
||
$limit = yii::$app->request->isPost ? 10 : 20;
|
||
|
||
if ($response = Product::searchByPartialCatn($query, $limit, [
|
||
'_key' => '_key',
|
||
'catn' => 'catn',
|
||
'name' => 'name',
|
||
// Баг с названием DESC
|
||
'dscr' => 'dscr',
|
||
'catg' => 'catg',
|
||
'imgs' => 'imgs',
|
||
'prod' => 'prod',
|
||
'dmns' => 'dmns',
|
||
])) {
|
||
// Данные найдены по поиску в полях Каталожного номера
|
||
|
||
foreach ($response as &$row) {
|
||
// Перебор продуктов
|
||
|
||
// Поиск поставок привязанных к продуктам
|
||
$connections = Supply::searchByEdge(
|
||
from: 'product',
|
||
to: 'supply',
|
||
edge: 'supply_edge_product',
|
||
limit: 11,
|
||
direction: 'OUTBOUND',
|
||
subquery_where: [
|
||
['product._key' => $row['_key']],
|
||
['supply.catn == product.catn'],
|
||
['supply_edge_product.type' => 'connect']
|
||
],
|
||
where: 'supply._id == supply_edge_product[0]._from',
|
||
select: '{supply, supply_edge_product}'
|
||
);
|
||
|
||
// Инициализация буфера
|
||
$buffer_connections = [];
|
||
|
||
if (count($connections) === 11) {
|
||
// Если в базе данных хранится много поставок
|
||
|
||
// Инициализация
|
||
$row['overload'] = true;
|
||
}
|
||
|
||
foreach ($connections as &$connection) {
|
||
// Перебор поставок
|
||
|
||
// Инициализация аккаунта
|
||
$connection['account'] = Account::searchBySupplyId($connection['supply_edge_product'][0]['_from']);
|
||
|
||
// Инициализация продукта
|
||
$connection['product'] = Product::searchBySupplyId($connection['supply_edge_product'][0]['_from']);
|
||
|
||
try {
|
||
// Инициализация данных геолокации
|
||
|
||
try {
|
||
$from = (int) $connection['account']['opts']['delivery_from_terminal'] ?? Settings::search()->delivery_from_default ?? 36;
|
||
} catch (Exception $e) {
|
||
$from = (int) Settings::search()->delivery_from_default ?? 36;
|
||
}
|
||
|
||
try {
|
||
$to = (int) yii::$app->user->identity->opts['delivery_to_terminal'] ?? 36;
|
||
} catch (Exception $e) {
|
||
$to = 36;
|
||
}
|
||
|
||
if (
|
||
($buffer_connection = $connection['product']['bffr']["$from-$to"] ?? false)
|
||
&& time() < $buffer_connection['expires']
|
||
) {
|
||
// Найдены данные доставки в буфере
|
||
// и срок хранения не превышен, информация актуальна
|
||
|
||
// Запись в буфер вывода
|
||
$connection['delivery'] = $buffer_connection['data'];
|
||
$connection['delivery']['type'] = 'auto';
|
||
} else {
|
||
// Инициализация инстанции продукта в базе данных
|
||
$product = Product::searchByCatn($connection['product']['catn']);
|
||
|
||
// Инициализация доставки Dellin (автоматическая)
|
||
$product->bffr = [
|
||
"$from-$to" => [
|
||
'data' => $connection['delivery'] = Dellin::calcDeliveryAdvanced(
|
||
$from,
|
||
$to,
|
||
(int) ($connection['product']['wght'] ?? 0),
|
||
(int) ($connection['product']['dmns']['x'] ?? 0),
|
||
(int) ($connection['product']['dmns']['y'] ?? 0),
|
||
(int) ($connection['product']['dmns']['z'] ?? 0)
|
||
),
|
||
'expires' => time() + 86400
|
||
]
|
||
] + ($product->bffr ?? []);
|
||
$connection['delivery']['type'] = 'auto';
|
||
|
||
// Отправка в базу данных
|
||
$product->update();
|
||
}
|
||
} catch (Exception $e) {
|
||
$connection['delivery']['error'] = true;
|
||
|
||
// var_dump($e->getMessage());
|
||
// var_dump($e->getTrace());
|
||
// var_dump($e->getFile());
|
||
|
||
// var_dump(json_decode($e->getMessage(), true)['errors']);
|
||
// die;
|
||
}
|
||
|
||
// Инициализация цены (цена поставки + цена доставки + наша наценка)
|
||
$connection['cost'] = ($cost['ЦенаЗаЕдиницу'] ?? $connection['supply_edge_product'][0]['onec']['Цены']['Цена']['ЦенаЗаЕдиницу']) + ($connection['delivery']['price']['all'] ?? $connection['delivery']['price']['one'] ?? 0) + ($settings['increase'] ?? 0);
|
||
|
||
// Инициализация версии для рассчета доставки по воздуху
|
||
$buffer_delivery_avia = $connection;
|
||
|
||
try {
|
||
// Инициализация данных геолокации
|
||
|
||
try {
|
||
$from = (int) $buffer_delivery_avia['account']['opts']['delivery_from_terminal'] ?? Settings::search()->delivery_from_default ?? 36;
|
||
} catch (Exception $e) {
|
||
$from = (int) Settings::search()->delivery_from_default ?? 36;
|
||
}
|
||
|
||
try {
|
||
$to = (int) yii::$app->user->identity->opts['delivery_to_terminal'] ?? 36;
|
||
} catch (Exception $e) {
|
||
$to = 36;
|
||
}
|
||
|
||
if (
|
||
($buffer_connection = $buffer_delivery_avia['product']['bffr']["$from-$to-avia"] ?? false)
|
||
&& time() < $buffer_connection['expires']
|
||
) {
|
||
// Найдены данные доставки в буфере
|
||
// и срок хранения не превышен, информация актуальна
|
||
|
||
// Запись в буфер вывода
|
||
$buffer_delivery_avia['delivery'] = $buffer_connection['data'];
|
||
$buffer_delivery_avia['delivery']['type'] = 'avia';
|
||
} else {
|
||
// Инициализация инстанции продукта в базе данных
|
||
$product = Product::searchByCatn($buffer_delivery_avia['product']['catn']);
|
||
|
||
// Инициализация доставки Dellin (автоматическая)
|
||
$product->bffr = [
|
||
"$from-$to-avia" => [
|
||
'data' => $buffer_delivery_avia['delivery'] = Dellin::calcDeliveryAdvanced(
|
||
$from,
|
||
$to,
|
||
(int) ($buffer_delivery_avia['product']['wght'] ?? 0),
|
||
(int) ($buffer_delivery_avia['product']['dmns']['x'] ?? 0),
|
||
(int) ($buffer_delivery_avia['product']['dmns']['y'] ?? 0),
|
||
(int) ($buffer_delivery_avia['product']['dmns']['z'] ?? 0),
|
||
avia: true
|
||
),
|
||
'expires' => time() + 86400
|
||
]
|
||
] + ($product->bffr ?? []);
|
||
$buffer_delivery_avia['delivery']['type'] = 'avia';
|
||
|
||
// Отправка в базу данных
|
||
$product->update();
|
||
}
|
||
} catch (Exception $e) {
|
||
$buffer_delivery_avia['delivery']['error'] = true;
|
||
|
||
// var_dump($e->getMessage());
|
||
// var_dump($e->getTrace());
|
||
// var_dump($e->getFile());
|
||
|
||
// var_dump(json_decode($e->getMessage(), true)['errors']);
|
||
// die;
|
||
}
|
||
|
||
if (!isset($buffer_delivery_avia['delivery']['error']) || $buffer_delivery_avia['delivery']['error'] !== true) {
|
||
// Если рассчиталась доставка самолётом
|
||
|
||
// Инициализация цены (цена поставки + цена доставки + наша наценка)
|
||
$buffer_delivery_avia['cost'] = ($cost['ЦенаЗаЕдиницу'] ?? $buffer_delivery_avia['supply_edge_product'][0]['onec']['Цены']['Цена']['ЦенаЗаЕдиницу']) + ($buffer_delivery_avia['delivery']['price']['all'] ?? $buffer_delivery_avia['delivery']['price']['one'] ?? 0) + ($settings['increase'] ?? 0);
|
||
|
||
// Запись в буфер
|
||
$buffer_connections[] = $buffer_delivery_avia;
|
||
}
|
||
}
|
||
|
||
// Запись обработанных данных
|
||
$row['supplies'] = array_merge($connections, $buffer_connections);
|
||
}
|
||
|
||
// Запись ответа
|
||
$return = [
|
||
'panel' => $this->renderPartial('/search/panel', compact('response')),
|
||
'_csrf' => yii::$app->request->getCsrfToken()
|
||
];
|
||
|
||
if ((int) yii::$app->request->post('advanced')) {
|
||
// Полноценный поиск
|
||
|
||
// Запись ответа
|
||
$return['main'] = $this->renderPartial('/search/index', compact('response'));
|
||
$return['hide'] = 1;
|
||
$return['redirect'] = '/search?type=product&q=' . $query;
|
||
}
|
||
} else {
|
||
// Данные не найдены
|
||
|
||
// Запись кода ответа: 404 (запрашиваемые данные не найдены)
|
||
yii::$app->response->statusCode = 404;
|
||
}
|
||
}
|
||
|
||
// Метка: "Пропуск запроса" (для пропуска самого поиска и возврата ответа)
|
||
skip_query:
|
||
|
||
if (yii::$app->request->isPost) {
|
||
// POST-запрос
|
||
|
||
yii::$app->response->format = Response::FORMAT_JSON;
|
||
|
||
return $return ?? [
|
||
'panel' => $this->renderPartial('/search/panel'),
|
||
'_csrf' => yii::$app->request->getCsrfToken()
|
||
];
|
||
} else {
|
||
// GET-запрос
|
||
|
||
if (empty($return['main']) && $keep_connect && $timer > 0) {
|
||
// Режим непрерывного соединения
|
||
|
||
// Ожидание
|
||
sleep($timer);
|
||
|
||
// Повтор обработки
|
||
goto keep_connect_wait;
|
||
}
|
||
|
||
$advanced = true;
|
||
|
||
return $this->render('/search/index', compact('response', 'timer', 'advanced'));
|
||
}
|
||
}
|
||
|
||
// Метка: "Пропуск обработки"
|
||
skip_search:
|
||
|
||
if (yii::$app->request->isPost) {
|
||
// POST-запрос
|
||
|
||
yii::$app->response->format = Response::FORMAT_JSON;
|
||
|
||
return [
|
||
'panel' => $this->renderPartial('/search/panel'),
|
||
'hide' => (int) $auth_only,
|
||
'_csrf' => yii::$app->request->getCsrfToken()
|
||
];
|
||
} else {
|
||
// GET-запрос
|
||
|
||
return $this->render('/search/index');
|
||
}
|
||
}
|
||
}
|