Ajax, REST API OpenCart

Категория: Программирование | Скилл: opencart , php , js | Дата: 11.01.2021

В статье рассмотрим как устроены 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 запрос достаточно в файл представления (читай в html) подставить js (код для OpenCart 2.3):
$.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),
который возвращается в json этого ajax запроса, если доступ по api (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 {
	...
}

Ajax запросы через 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 достаточно изучить что это такое, несколько ссылок: