В статье рассмотрим как устроены ajax запросы в OpenCart, в том числе запросы через api OpenCart, познакомимся с новым понятием front controller и немного коснемся темы ajax REST API.
Клиент
Клиентская часть OpenCart работает с использованием jquery, а значит можно использовать $.ajax
из этой библиотеки. Ссылка на документацию. Примеры ajax запросов на клиентской части можно посмотреть в admin/view/template/sale/order_form.tpl
(.twig для OpenCart 3.0).
Сервер
Просматривая все тот же файл admin/view/template/sale/order_form.tpl
(для OpenCart 2.3) можно понять, что в качестве адреса вызова используется классическая схема роутинга OpenCart. Посмотрим на один из запросов:
$.ajax({ url: 'index.php?route=customer/customer/autocomplete&token=<?php echo $token; ?>&filter_name=' + encodeURIComponent(request), ...
Все просто: url - путь до контроллера, и если надо имя метода этого контроллера.
То есть, нам нужно создать класс контроллера, затем из файлов представления можно вызывать методы этого контроллера ajax запросами.
Создадим контроллер нашего нового тестового модуля по пути admin/controller/extension/module/myajax.php
:
class ControllerExtensionModuleMyAjax extends Controller { public function index() { $this->response->addHeader('Content-Type: application/json'); $this->response->setOutput(json_encode( [ "success" => true, "message" => "ok", "data" => [] ] )); } }
В классе контроллера есть объект response
, это экземпляр класса Response
который расположен по пути system/library/response.php
. Он позволяет управлять ответом сервера. Нас интересуют только 2 метода:
addHeader($header)
- добавить http заголовок,header
строковый аргументsetOutput($output)
- установить данные для вывода,output
строковый аргумент
$this->response
Так как OpenCart имеет 2 режима доступа/контекста (admin, catalog), то передаваемые данные в запросах разные:
admin
- требует токен вget
параметре (получить можно из объекта класса контроллера):- для OpenCart 2.3
token
, который берется из$this->session->data['token']
- для OpenCart 3.0
user_token
, который берется из$this->session->data['user_token']
catalog
- в общем случае не требует токена, но есть нюансы о которых позже
$.ajax({ url: '<?php echo $admin; ?>index.php?route=extesion/module/myajax&token=<?php echo $token; ?>', type: 'get', dataType: 'json', success: function(json) { alert("success: "+json["success"]+"\n"+"message: "+json["message"]); }, error: function(xhr, ajaxOptions, thrownError) { alert(thrownError + "\r\n" + xhr.statusText + "\r\n" + xhr.responseText); } });
В этом коде в url admin
это путь указывающий контекст запроса (admin или catalog). Для контекста есть 2 дефайна определенных в admin/config
:
HTTP_SERVER
илиHTTPS_SERVER
- путь до директорииadmin
(проще - админка), где будет осуществлен поиск контроллера для выполнения запросаHTTP_CATALOG
илиHTTPS_CATALOG
- корень сайта, однако контроллеры будут браться из директорииcatalog
$this->request
В ходе эксплуатации разработанного модуля, вылезла одна особенность OpenCart:
$this->request->post != $_POST
так как system/library/request.php
производит обработку данных через htmlspecialchars
. Это касается и других суперглобальных переменных php
: $_GET
, $_REQUEST
, $_COOKIE
, $_FILES
, $_SERVER
Инцидент: в интерфейсе админки был input
, данные которого передавались по ajax
, а там запись в БД данных из $this->request->post
. Но так получилось, что в текст (DataMatrix) попали кавычки ... и записались в БД неверно. Конечно, можно было бы после чтения из БД применить декодирование, однако данные поступают только из админки, и перед записью в БД проходят через $this->db->escape
поэтому проблема была решена использованием данных из $_POST
.
Ajax API
Просматривая файл представления admin/view/template/sale/order_form.tpl
(OpenCart 2.3), можно увидеть что из админки осуществляются ajax запросы на catalog
контекст, с использованием особого токена.
Сначала объявляется глобальная переменная token
, затем ajax запрос на адрес /index.php?route=api/login
, который отвечает json данными в которых есть ключ token
:
var token = ''; // Login to the API $.ajax({ url: '<?php echo $catalog; ?>index.php?route=api/login', type: 'post', data: 'key=<?php echo $api_key; ?>', dataType: 'json', crossDomain: true, success: function(json) { //... if (json['token']) { token = json['token']; } }, error: function(xhr, ajaxOptions, thrownError) { alert(thrownError + "\r\n" + xhr.statusText + "\r\n" + xhr.responseText); } });
Контроллер этого запроса находится в catalog/controller/api/login.php ControllerApiLogin::index
. Он:
- создет новую сессию (
catalog/model/account/api.php - ModelAccountApi::addApiSession
) и - генерирует для нее случайный токен (функция
token
находится вsystem/helper/general.php
),
api_key
) разрешен для текущего пользователя (Админка-Система-API).Дальше разбирая представление admin/view/template/sale/order_form.tpl
можно увидеть что последующие ajax запросы, которые по адресу route=api/...
используют этот самый token
для определения права доступа, таким образом (в каждом api файле, в каждом методе) сущетвует такой кусок кода для определения права осуществлять запрос:
if (!isset($this->session->data['api_id'])) { $json['error']['warning'] = $this->language->get('error_permission'); } else { ... }
catalog
контекст можно осуществлять с использованием token
для безопасного доступаА теперь копнем глубже и выясним как это происходит внутри движка, ведь можно отправлять ajax запросы и без токена.
Просматривая код файла index.php
отправляемя в system/startup.php
, оттуда следуем в system/framework.php
в самый конец и видим такое вот:
// Front Controller $controller = new Front($registry); // Pre Actions if ($config->has('action_pre_action')) { foreach ($config->get('action_pre_action') as $value) { $controller->addPreAction(new Action($value)); } }
Здесь видим новое понятие front controller, код которого находится в system/engine/front.php
в классе Front
.
Ниже следует мое субьективное определение этого понятия :)
Подробных комментариев найти не удалось, но судя по коду front controller это главный/передний контроллер, он запускает общий контроллер startup/router
относительно директории controller
контекста (admin/controller
или catalog/controller
), который выполняет первичные контроллеры указанные в $_['action_pre_action'];
в файле system/config/catalog.php
.
В коде выше происходит только добавление первичных контроллеров во front controller, а их исполнение осуществляется кодом ниже в методе dispatch
(внутри метода перед выполнением action указанного в $config->get('action_router')
):
// Dispatch $controller->dispatch(new Action($config->get('action_router')), new Action($config->get('action_error')));
Среди первичных контроллеров есть startup/session
относительно catalog/controller
где в ControllerStartupSession::index
находится интересующий нас код для авторизации в api через токен. Вкратце:
- происходит проверка обращения к
api/
и наличияget
параметраtoken
- удаление старых api сессий
- выборка актуальной api сессии на основании ip адреса запросившего и его токена
- старт сессии с id из
$_COOKIE["api"]
- обновление времени модификации сессии (чтобы они осталась жива, то есть не была устаревшей)
$this->session->data['api_id']
уже будет иницилизировано если указана актуальная комбинация токена и ip адреса.Ajax REST API
Данная глава описывает возможный вариант создания и встроенные средства реализации REST API в OpenCart.
Мы рассмотрели реализацию ajax запросов OpenCart для admin
и catalog
контекстов.
Если говорить об admin
, то предполагается более рациональным реализовывать контроллеры именно в admin
контексте. Однако, такое не всегда возможно. Иногда один и тот же код контроллера (возможно речь о методе контроллера) должен использоваться в обработчике catalog
события (например при изменении заказа), так и отдельно непосредственно при работе с заказом через админку. Чтобы устранить такие случаи можно реализовать контроллеры в catalog
контексте и организовать для них безопасный доступ (о чем говорится в предыдущей главе).
Для реализации REST API в OpenCart есть все необходимое:
- объект для работы с ответом сервера в контроллере
$this->response
, а именно методыaddHeader
иsetOutput
(глава Сервер) - безопасная работа с административным доступом через
catalog
контекст (глава Ajax API) - единая точка входа api через
catalog
контекст, в директориюcatalog/controller/api/
можно размещать свои файлы контроллеров и при помощи ajax осуществлять к ним запросы
catalog/controller/api/
, а на стороне клиента добавить ajax запросы в нужные файлы представлений с использованием токена полученного в результате ajax запроса api/login
. Если в этих файлах нет такого ajax запроса, тогда необходимо добавить его, например взяв из admin/view/template/sale/order_form.tpl
Теперь чтобы сделать REST API достаточно изучить что это такое, несколько ссылок: