В предыдущих двух записях, посвященных разработке модулей для OpenCart мы разобрались, как написать достаточно простой модуль на примере счетчика Яндекс.Метрики. Ссылки на 2 части статьи вы найдете в под этим текстом.
Сегодня мы научимся писать что-то более интересное и сложное. Недавно мне пришла задача:
Написать «модуль» для отображения иконок на странице товара, которые были предварительно заданы в админке. Иконки должны задаваться на уровне категорий, т.е. указываются для всех товаров выбранной категории сразу. Так же, у Иконок есть Группы, которые тоже нужно создать и присваивать. Должен задаваться порядок сортировки как Групп, так и Иконок в группах. На странице товаров иконки должны выводиться 2 раза. Сначала просто блоком иконок со всплывающими подсказками, а потом отдельной вкладкой, в которой иконки будут уже отображаться с доп. полями, а именно названием и описанием.
Ну, нас таким не напугаешь. Главное, все продумать сначала, а потом уже делать. Как-то на одном собеседовании на Full-stack Developer меня спросили: «Как бы вы поделили свое рабочее время между обсуждением и программированием». Я ответи «80/20». Т.е. 80% времени уделяется планированию, а 20% – программированию. Мы не говорим про рутинные и простые задачи, а скорее про такие, как эта, где нужно сначала все продумать и построить. Если Вам дали задачу покрасить кнопку – просто покрасьте кнопку :)
Административная часть
Из предыдущих статей Вы уже знаете, что OpenCart это MVC(L) система с признаками фреймворка :) Самое важно на текущем этапе, так это то, что у нее отделены друг от друга Пользовательская и Административная часть. Для такой доработки, как описанная выше разумно начать с Административной части, т.к. многое из того, что вы напишите для Админки с успехом будет использовано для Пользовательской части.
Модель
Начинать все сложные доработки, требующие хранения сущностей в базе данных, я советую именно с Модели. Во-первых, добавление новых таблиц в БД не приводит к ошибкам на самом сайте. Я стараюсь исповедовать такой метод разработки, когда сайт и административная часть доступы всегда, если вы дописали код до какой-то логической точки.
Вернемся к нашей БД. У OpenCart хорошо продуманная система БД (ну, разве что кроме дополнительных фото). Я Вам настоятельно рекомендую не изобретать велосипед при разработке, а следовать стандартам. Для нашего модуля под условия ТЗ я решил создать в БД 5 новых таблиц:
- Таблица
oc_function
с полями: function_id
, function_group_id
, sort_order
;
- Таблица
oc_function_description
с полями: function_id
, language_id
, name
, description
, image
;
- Таблица
oc_function_group
с полями: function_group_id
и sort_order
;
- Таблица
oc_function_group_description
с полями function_group_id
, language_id
, name
;
- Таблица
oc_function_to_category
с полями function_id
и category_id
.
Прошу заметить, что такая структура весьма привычна при разработке для OpenCart, если нужно создавать новую Сущность. Если вы достаточно хорошо разбираетесь в MySQL, то для экономии времени я прикладываю архив с sql, в котором не только создана эта структура, но и даже заполнены демо-данные. Обращаю Ваше внимание, что в sql-файле уже присутствуют префиксы таблиц OpenCart, по-умолчанию это oc_
.
После импорта у Вас должно получиться так:
Так же в самих таблицах уже имеются какие-то записи для того, чтобы вы не работали с пустыми данными.
Теперь можно написать методы в саму модель, чтобы работать с этими таблицами.
Создадим новый файлик в admin/model/catalog/
и назовем его function.php
со следующим содержимым:
<?php class ModelCatalogFunction extends Model { public function addFunction($data) { $this->db->query("INSERT INTO " . DB_PREFIX . "function SET function_group_id = '" . (int)$data['function_group_id'] . "', sort_order = '" . (int)$data['sort_order'] . "'");
$function_id = $this->db->getLastId();
foreach ($data['function_description'] as $language_id => $value) {
$this->db->query("INSERT INTO " . DB_PREFIX . "function_description SET function_id = '" . (int)$function_id . "', language_id = '" . (int)$language_id . "', name = '" . $this->db->escape($value['name']) . "', description = '" . $this->db->escape($value['description']) . "'");
}
if (isset($data['image'])) {
$this->db->query("UPDATE " . DB_PREFIX . "function_description SET image = '" . $this->db->escape($data['image']) . "' WHERE function_id = '" . (int)$function_id . "'");
}
return $function_id;
}
public function editFunction($function_id, $data) {
$this->db->query("UPDATE " . DB_PREFIX . "function SET function_group_id = '" . (int)$data['function_group_id'] . "', sort_order = '" . (int)$data['sort_order'] . "' WHERE function_id = '" . (int)$function_id . "'");
$this->db->query("DELETE FROM " . DB_PREFIX . "function_description WHERE function_id = '" . (int)$function_id . "'");
foreach ($data['function_description'] as $language_id => $value) {
$this->db->query("INSERT INTO " . DB_PREFIX . "function_description SET function_id = '" . (int)$function_id . "', language_id = '" . (int)$language_id . "', name = '" . $this->db->escape($value['name']) . "', description = '" . $this->db->escape($value['description']) . "'");
}
if (isset($data['image'])) {
$this->db->query("UPDATE " . DB_PREFIX . "function_description SET image = '" . $this->db->escape($data['image']) . "' WHERE function_id = '" . (int)$function_id . "'");
}
}
public function deleteFunction($function_id) {
$this->db->query("DELETE FROM " . DB_PREFIX . "function WHERE function_id = '" . (int)$function_id . "'");
$this->db->query("DELETE FROM " . DB_PREFIX . "function_description WHERE function_id = '" . (int)$function_id . "'");
}
public function getFunction($function_id) {
$query = $this->db->query("SELECT * FROM " . DB_PREFIX . "function a LEFT JOIN " . DB_PREFIX . "function_description ad ON (a.function_id = ad.function_id) WHERE a.function_id = '" . (int)$function_id . "' AND ad.language_id = '" . (int)$this->config->get('config_language_id') . "'");
return $query->row;
}
public function getFunctions($data = array()) {
$sql = "SELECT *, (SELECT agd.name FROM " . DB_PREFIX . "function_group_description agd WHERE agd.function_group_id = a.function_group_id AND agd.language_id = '" . (int)$this->config->get('config_language_id') . "') AS function_group FROM " . DB_PREFIX . "function a LEFT JOIN " . DB_PREFIX . "function_description ad ON (a.function_id = ad.function_id) WHERE ad.language_id = '" . (int)$this->config->get('config_language_id') . "'";
if (!empty($data['filter_name'])) {
$sql .= " AND ad.name LIKE '" . $this->db->escape($data['filter_name']) . "%'";
}
if (!empty($data['filter_function_group_id'])) {
$sql .= " AND a.function_group_id = '" . $this->db->escape($data['filter_function_group_id']) . "'";
}
$sort_data = array(
'ad.name',
'function_group',
'a.sort_order'
);
if (isset($data['sort']) && in_array($data['sort'], $sort_data)) {
$sql .= " ORDER BY " . $data['sort'];
} else {
$sql .= " ORDER BY function_group, ad.name";
}
if (isset($data['order']) && ($data['order'] == 'DESC')) {
$sql .= " DESC";
} else {
$sql .= " ASC";
}
if (isset($data['start']) || isset($data['limit'])) {
if ($data['start'] < 0) {
$data['start'] = 0;
}
if ($data['limit'] < 1) { $data['limit'] = 20; } $sql .= " LIMIT " . (int)$data['start'] . "," . (int)$data['limit']; } $query = $this->db->query($sql);
return $query->rows;
}
public function getFunctionDescriptions($function_id) {
$function_data = array();
$query = $this->db->query("SELECT * FROM " . DB_PREFIX . "function_description WHERE function_id = '" . (int)$function_id . "'");
foreach ($query->rows as $result) {
$function_data[$result['language_id']] = array(
'name' => $result['name'],
'description' => $result['description'],
);
}
return $function_data;
}
public function getTotalFunctions() {
$query = $this->db->query("SELECT COUNT(*) AS total FROM " . DB_PREFIX . "function");
return $query->row['total'];
}
public function getTotalFunctionsByFunctionGroupId($function_group_id) {
$query = $this->db->query("SELECT COUNT(*) AS total FROM " . DB_PREFIX . "function WHERE function_group_id = '" . (int)$function_group_id . "'");
return $query->row['total'];
}
}
Файл может Вас немного напугать, но давайте разберем его на составляющие и Вы поймете, что там ничего сложного нет. Во-первых, перечислим основные Методы, которые мы создали:
addFunction($data)
– создание новой функции;
editFunction($function_id, $data)
– обновление (изменение) функции с указанным function_id
;
deleteFunction($function_id)
– удалить функцию с указанным function_id
. По идее тут еще должна быть проверка, что удаляемая Функция не присвоена какой-то Категории товаров, но ее тут нет. Если у Вас есть академическое желание, вы можете подсмотреть реализацию этой проверки в других моделях, например, Атрибутов;
getFunction($function_id)
– получить информацию об функции с ID = function_id
;
getFunctions($data = array())
– получить список всех функций, массив $data
используется для фильтрации данных по каким-то признакам;
getFunctionDescriptions($function_id)
– получить описание функции с ID = function_id
;
getTotalFunctions()
– возвращает количество всех Функций, можно использовать для Пагинации;
getTotalFunctionsByFunctionGroupId($function_group_id)
– возвращает количество функций в определенной Групы по function_group_id
;
В базовом варианте вам вполне хватит первых 6 методов для успешной работы модуля. Два оставшихся используются для Пагинации и не являются обязательными.
Теперь нужно создать Модель для обработки Категорий наших Функций.
Создадим новый файлик в admin/model/catalog/
и назовем его function_group.php
со следующим содержимым:
< ?php class ModelCatalogFunctionGroup extends Model { public function addFunctionGroup($data) { $this->db->query("INSERT INTO " . DB_PREFIX . "function_group SET sort_order = '" . (int)$data['sort_order'] . "'");
$function_group_id = $this->db->getLastId();
foreach ($data['function_group_description'] as $language_id => $value) {
$this->db->query("INSERT INTO " . DB_PREFIX . "function_group_description SET function_group_id = '" . (int)$function_group_id . "', language_id = '" . (int)$language_id . "', name = '" . $this->db->escape($value['name']) . "'");
}
return $function_group_id;
}
public function editFunctionGroup($function_group_id, $data) {
$this->db->query("UPDATE " . DB_PREFIX . "function_group SET sort_order = '" . (int)$data['sort_order'] . "' WHERE function_group_id = '" . (int)$function_group_id . "'");
$this->db->query("DELETE FROM " . DB_PREFIX . "function_group_description WHERE function_group_id = '" . (int)$function_group_id . "'");
foreach ($data['function_group_description'] as $language_id => $value) {
$this->db->query("INSERT INTO " . DB_PREFIX . "function_group_description SET function_group_id = '" . (int)$function_group_id . "', language_id = '" . (int)$language_id . "', name = '" . $this->db->escape($value['name']) . "'");
}
}
public function deleteFunctionGroup($function_group_id) {
$this->db->query("DELETE FROM " . DB_PREFIX . "function_group WHERE function_group_id = '" . (int)$function_group_id . "'");
$this->db->query("DELETE FROM " . DB_PREFIX . "function_group_description WHERE function_group_id = '" . (int)$function_group_id . "'");
}
public function getFunctionGroup($function_group_id) {
$query = $this->db->query("SELECT * FROM " . DB_PREFIX . "function_group WHERE function_group_id = '" . (int)$function_group_id . "'");
return $query->row;
}
public function getFunctionGroups($data = array()) {
$sql = "SELECT * FROM " . DB_PREFIX . "function_group ag LEFT JOIN " . DB_PREFIX . "function_group_description agd ON (ag.function_group_id = agd.function_group_id) WHERE agd.language_id = '" . (int)$this->config->get('config_language_id') . "'";
$sort_data = array(
'agd.name',
'ag.sort_order'
);
if (isset($data['sort']) && in_array($data['sort'], $sort_data)) {
$sql .= " ORDER BY " . $data['sort'];
} else {
$sql .= " ORDER BY agd.name";
}
if (isset($data['order']) && ($data['order'] == 'DESC')) {
$sql .= " DESC";
} else {
$sql .= " ASC";
}
if (isset($data['start']) || isset($data['limit'])) {
if ($data['start'] < 0) {
$data['start'] = 0;
}
if ($data['limit'] < 1) { $data['limit'] = 20; } $sql .= " LIMIT " . (int)$data['start'] . "," . (int)$data['limit']; } $query = $this->db->query($sql);
return $query->rows;
}
public function getFunctionGroupDescriptions($function_group_id) {
$function_group_data = array();
$query = $this->db->query("SELECT * FROM " . DB_PREFIX . "function_group_description WHERE function_group_id = '" . (int)$function_group_id . "'");
foreach ($query->rows as $result) {
$function_group_data[$result['language_id']] = array('name' => $result['name']);
}
return $function_group_data;
}
public function getTotalFunctionGroups() {
$query = $this->db->query("SELECT COUNT(*) AS total FROM " . DB_PREFIX . "function_group");
return $query->row['total'];
}
}
Как и в предыдущем случае, рассмотрим Методы, которые мы создали:
addFunctionGroup($data)
– создает новую Группу Функций;
editFunctionGroup($function_group_id, $data)
– изменяет (обновляет_ Группу Функций с заданным function_id
;
deleteFunctionGroup($function_group_id)
– удаляет Группу Функций с заданным ID;
getFunctionGroup($function_group_id)
– скажем так, этим Методом можно проверить существование Группы. Для этого в OpenCart есть специальный метод драйвера БД $variable_after_query->num_rows
, но об этом поговорим в пользовательской части.
getFunctionGroups($data = array())
– возвращает список Групп Функций, поддерживает сортировки и фильт через массив $data
;
getFunctionGroupDescriptions($function_group_id)
– возвращает описание Группы Функции.
getTotalFunctionGroups()
– необязательный Метод, применим для Пагинации.
На этом создание Модели для административной части нашей доработки можно считать законченным. Я на самом деле создавал Методы поэтапно, но воспроизвести весь этот «тернистый путь» в рамках и так не простой статьи считаю излишеством. Если у Вас по прочтению статьи останутся вопросы, вы всегда можете задать их в комментариях.
Контроллер, Язык и Представление
Следующим логичным шагом будет конечно же создание остальной части нашего модуля. Давайте создадим языковый файл. Для этого в admin/language/ru-ru/catalog/
создадим два файла function.php
и function_group.php
Листинг function.php
<?php
// Heading
$_['heading_title'] = 'Функции';
// Text
$_['text_success'] = 'Функции успешно обновлены!';
$_['text_list'] = 'Список Функций';
$_['text_add'] = 'Добавление Функции';
$_['text_edit'] = 'Редактирование Функции';
// Column
$_['column_image'] = 'Иконка';
$_['column_name'] = 'Название';
$_['column_function_group'] = 'Группа';
$_['column_sort_order'] = 'Порядок сортировки';
$_['column_action'] = 'Действие';
// Entry
$_['entry_name'] = 'Название:';
$_['entry_function_group'] = 'Группа:';
$_['entry_description'] = 'Описание функции:';
$_['entry_image'] = 'Иконка функции:';
$_['entry_sort_order'] = 'Порядок сортировки:';
// Error
$_['error_permission'] = 'У вас нет прав для изменения Функций!';
$_['error_function_group'] = 'Не указана группа Функции!';
$_['error_name'] = 'Название Функции должно быть от 3 до 64 символов!';
$_['error_description'] = 'Описание Функции должно быть от 3 до 1000 символов!';
$_['error_product'] = 'Эта Функция не может быть удалена, так как назначена %s категорий!';
Листинг function_group.php
<?php
// Heading
$_['heading_title'] = 'Группы функций Daikin';
// Text
$_['text_success'] = 'Группы функций Daikin успешно обновлены!';
$_['text_list'] = 'Список групп функций Daikin';
$_['text_add'] = 'Добавление группы';
$_['text_edit'] = 'Редактирование группы';
// Column
$_['column_name'] = 'Название группы';
$_['column_sort_order'] = 'Порядок сортировки';
$_['column_action'] = 'Действие';
// Entry
$_['entry_name'] = 'Название';
$_['entry_sort_order'] = 'Порядок сортировки';
// Error
$_['error_permission'] = 'У вас нет прав для изменения групп атрибутов!';
$_['error_name'] = 'Название группы функций Daikin должно быть от 3 до 64 символов!';
$_['error_attribute'] = 'Группа функций Daikin не может быть удалена, поскольку она используется %s функциями!';
$_['error_product'] = 'Группа функций Daikin не может быть удалена, поскольку она используется %s функциями!';
Также, нужно добавить в Языковый файл главного меню две записи для корректного вывода названий нашего модуля, для этого в admin/language/ru-ru/common/column_left.php
добавить
$_['text_function'] = 'Функции';
$_['text_function_group'] = 'Группы функций';
Собственно, можно перейти к святая святых любого MVC – Контроллеру. Создадим в admin/controller/catalog/
два файла function.php
и function_group.php
Листинг function.php
<?php class ControllerCatalogFunction extends Controller { private $error = array(); public function index() { $this->load->language('catalog/function');
$this->document->setTitle($this->language->get('heading_title'));
$this->load->model('catalog/function');
$this->getList();
}
public function add() {
$this->load->language('catalog/function');
$this->document->setTitle($this->language->get('heading_title'));
$this->load->model('catalog/function');
if (($this->request->server['REQUEST_METHOD'] == 'POST') && $this->validateForm()) {
$this->model_catalog_function->addFunction($this->request->post);
$this->session->data['success'] = $this->language->get('text_success');
$url = '';
if (isset($this->request->get['sort'])) {
$url .= '&sort=' . $this->request->get['sort'];
}
if (isset($this->request->get['order'])) {
$url .= '&order=' . $this->request->get['order'];
}
if (isset($this->request->get['page'])) {
$url .= '&page=' . $this->request->get['page'];
}
$this->response->redirect($this->url->link('catalog/function', 'token=' . $this->session->data['token'] . $url, true));
}
$this->getForm();
}
public function edit() {
$this->load->language('catalog/function');
$this->document->setTitle($this->language->get('heading_title'));
$this->load->model('catalog/function');
if (($this->request->server['REQUEST_METHOD'] == 'POST') && $this->validateForm()) {
$this->model_catalog_function->editFunction($this->request->get['function_id'], $this->request->post);
$this->session->data['success'] = $this->language->get('text_success');
$url = '';
if (isset($this->request->get['sort'])) {
$url .= '&sort=' . $this->request->get['sort'];
}
if (isset($this->request->get['order'])) {
$url .= '&order=' . $this->request->get['order'];
}
if (isset($this->request->get['page'])) {
$url .= '&page=' . $this->request->get['page'];
}
$this->response->redirect($this->url->link('catalog/function', 'token=' . $this->session->data['token'] . $url, true));
}
$this->getForm();
}
public function delete() {
$this->load->language('catalog/function');
$this->document->setTitle($this->language->get('heading_title'));
$this->load->model('catalog/function');
// Временно закоментчено! Обязательно написать проверку на присвоение иконок категориям!!!
// if (isset($this->request->post['selected']) && $this->validateDelete()) {
if (isset($this->request->post['selected'])) {
foreach ($this->request->post['selected'] as $function_id) {
$this->model_catalog_function->deleteFunction($function_id);
}
$this->session->data['success'] = $this->language->get('text_success');
$url = '';
if (isset($this->request->get['sort'])) {
$url .= '&sort=' . $this->request->get['sort'];
}
if (isset($this->request->get['order'])) {
$url .= '&order=' . $this->request->get['order'];
}
if (isset($this->request->get['page'])) {
$url .= '&page=' . $this->request->get['page'];
}
$this->response->redirect($this->url->link('catalog/function', 'token=' . $this->session->data['token'] . $url, true));
}
$this->getList();
}
protected function getList() {
if (isset($this->request->get['sort'])) {
$sort = $this->request->get['sort'];
} else {
$sort = 'ad.name';
}
if (isset($this->request->get['order'])) {
$order = $this->request->get['order'];
} else {
$order = 'ASC';
}
if (isset($this->request->get['page'])) {
$page = $this->request->get['page'];
} else {
$page = 1;
}
$url = '';
if (isset($this->request->get['sort'])) {
$url .= '&sort=' . $this->request->get['sort'];
}
if (isset($this->request->get['order'])) {
$url .= '&order=' . $this->request->get['order'];
}
if (isset($this->request->get['page'])) {
$url .= '&page=' . $this->request->get['page'];
}
$data['breadcrumbs'] = array();
$data['breadcrumbs'][] = array(
'text' => $this->language->get('text_home'),
'href' => $this->url->link('common/dashboard', 'token=' . $this->session->data['token'], true)
);
$data['breadcrumbs'][] = array(
'text' => $this->language->get('heading_title'),
'href' => $this->url->link('catalog/function', 'token=' . $this->session->data['token'] . $url, true)
);
$data['add'] = $this->url->link('catalog/function/add', 'token=' . $this->session->data['token'] . $url, true);
$data['delete'] = $this->url->link('catalog/function/delete', 'token=' . $this->session->data['token'] . $url, true);
$data['functions'] = array();
$this->load->model('tool/image');
$filter_data = array(
'sort' => $sort,
'order' => $order,
'start' => ($page - 1) * $this->config->get('config_limit_admin'),
'limit' => $this->config->get('config_limit_admin')
);
$function_total = $this->model_catalog_function->getTotalFunctions();
$results = $this->model_catalog_function->getFunctions($filter_data);
foreach ($results as $result) {
if (is_file(DIR_IMAGE . $result['image'])) {
$image = $this->model_tool_image->resize($result['image'], 40, 40);
} else {
$image = $this->model_tool_image->resize('no_image.png', 40, 40);
}
$data['functions'][] = array(
'function_id' => $result['function_id'],
'image' => $image,
'name' => $result['name'],
'function_group' => $result['function_group'],
'sort_order' => $result['sort_order'],
'edit' => $this->url->link('catalog/function/edit', 'token=' . $this->session->data['token'] . '&function_id=' . $result['function_id'] . $url, true)
);
}
$data['heading_title'] = $this->language->get('heading_title');
$data['text_list'] = $this->language->get('text_list');
$data['text_no_results'] = $this->language->get('text_no_results');
$data['text_confirm'] = $this->language->get('text_confirm');
$data['column_image'] = $this->language->get('column_image');
$data['column_name'] = $this->language->get('column_name');
$data['column_function_group'] = $this->language->get('column_function_group');
$data['column_sort_order'] = $this->language->get('column_sort_order');
$data['column_action'] = $this->language->get('column_action');
$data['button_add'] = $this->language->get('button_add');
$data['button_edit'] = $this->language->get('button_edit');
$data['button_delete'] = $this->language->get('button_delete');
if (isset($this->error['warning'])) {
$data['error_warning'] = $this->error['warning'];
} else {
$data['error_warning'] = '';
}
if (isset($this->session->data['success'])) {
$data['success'] = $this->session->data['success'];
unset($this->session->data['success']);
} else {
$data['success'] = '';
}
if (isset($this->request->post['selected'])) {
$data['selected'] = (array)$this->request->post['selected'];
} else {
$data['selected'] = array();
}
$url = '';
if ($order == 'ASC') {
$url .= '&order=DESC';
} else {
$url .= '&order=ASC';
}
if (isset($this->request->get['page'])) {
$url .= '&page=' . $this->request->get['page'];
}
$data['sort_name'] = $this->url->link('catalog/function', 'token=' . $this->session->data['token'] . '&sort=ad.name' . $url, true);
$data['sort_function_group'] = $this->url->link('catalog/function', 'token=' . $this->session->data['token'] . '&sort=function_group' . $url, true);
$data['sort_sort_order'] = $this->url->link('catalog/function', 'token=' . $this->session->data['token'] . '&sort=a.sort_order' . $url, true);
$url = '';
if (isset($this->request->get['sort'])) {
$url .= '&sort=' . $this->request->get['sort'];
}
if (isset($this->request->get['order'])) {
$url .= '&order=' . $this->request->get['order'];
}
$pagination = new Pagination();
$pagination->total = $function_total;
$pagination->page = $page;
$pagination->limit = $this->config->get('config_limit_admin');
$pagination->url = $this->url->link('catalog/function', 'token=' . $this->session->data['token'] . $url . '&page={page}', true);
$data['pagination'] = $pagination->render();
$data['results'] = sprintf($this->language->get('text_pagination'), ($function_total) ? (($page - 1) * $this->config->get('config_limit_admin')) + 1 : 0, ((($page - 1) * $this->config->get('config_limit_admin')) > ($function_total - $this->config->get('config_limit_admin'))) ? $function_total : ((($page - 1) * $this->config->get('config_limit_admin')) + $this->config->get('config_limit_admin')), $function_total, ceil($function_total / $this->config->get('config_limit_admin')));
$data['sort'] = $sort;
$data['order'] = $order;
$data['header'] = $this->load->controller('common/header');
$data['column_left'] = $this->load->controller('common/column_left');
$data['footer'] = $this->load->controller('common/footer');
$this->response->setOutput($this->load->view('catalog/function_list', $data));
}
protected function getForm() {
$data['heading_title'] = $this->language->get('heading_title');
$data['text_form'] = !isset($this->request->get['function_id']) ? $this->language->get('text_add') : $this->language->get('text_edit');
$data['entry_name'] = $this->language->get('entry_name');
$data['entry_function_group'] = $this->language->get('entry_function_group');
$data['entry_description'] = $this->language->get('entry_description');
$data['entry_image'] = $this->language->get('entry_image');
$data['entry_sort_order'] = $this->language->get('entry_sort_order');
$data['button_save'] = $this->language->get('button_save');
$data['button_cancel'] = $this->language->get('button_cancel');
if (isset($this->error['warning'])) {
$data['error_warning'] = $this->error['warning'];
} else {
$data['error_warning'] = '';
}
if (isset($this->error['name'])) {
$data['error_name'] = $this->error['name'];
} else {
$data['error_name'] = array();
}
if (isset($this->error['description'])) {
$data['error_description'] = $this->error['description'];
} else {
$data['error_description'] = array();
}
if (isset($this->error['function_group'])) {
$data['error_function_group'] = $this->error['function_group'];
} else {
$data['error_function_group'] = '';
}
$url = '';
if (isset($this->request->get['sort'])) {
$url .= '&sort=' . $this->request->get['sort'];
}
if (isset($this->request->get['order'])) {
$url .= '&order=' . $this->request->get['order'];
}
if (isset($this->request->get['page'])) {
$url .= '&page=' . $this->request->get['page'];
}
$data['breadcrumbs'] = array();
$data['breadcrumbs'][] = array(
'text' => $this->language->get('text_home'),
'href' => $this->url->link('common/dashboard', 'token=' . $this->session->data['token'], true)
);
$data['breadcrumbs'][] = array(
'text' => $this->language->get('heading_title'),
'href' => $this->url->link('catalog/function', 'token=' . $this->session->data['token'] . $url, true)
);
if (!isset($this->request->get['function_id'])) {
$data['action'] = $this->url->link('catalog/function/add', 'token=' . $this->session->data['token'] . $url, true);
} else {
$data['action'] = $this->url->link('catalog/function/edit', 'token=' . $this->session->data['token'] . '&function_id=' . $this->request->get['function_id'] . $url, true);
}
$data['cancel'] = $this->url->link('catalog/function', 'token=' . $this->session->data['token'] . $url, true);
if (isset($this->request->get['function_id']) && ($this->request->server['REQUEST_METHOD'] != 'POST')) {
$function_info = $this->model_catalog_function->getFunction($this->request->get['function_id']);
}
$this->load->model('localisation/language');
$data['languages'] = $this->model_localisation_language->getLanguages();
if (isset($this->request->post['function_description'])) {
$data['function_description'] = $this->request->post['function_description'];
} elseif (isset($this->request->get['function_id'])) {
$data['function_description'] = $this->model_catalog_function->getFunctionDescriptions($this->request->get['function_id']);
} else {
$data['function_description'] = array();
}
if (isset($this->request->post['image'])) {
$data['image'] = $this->request->post['image'];
} elseif (!empty($function_info)) {
$data['image'] = $function_info['image'];
} else {
$data['image'] = '';
}
$this->load->model('tool/image');
if (isset($this->request->post['image']) && is_file(DIR_IMAGE . $this->request->post['image'])) {
$data['thumb'] = $this->model_tool_image->resize($this->request->post['image'], 100, 100);
} elseif (!empty($function_info) && is_file(DIR_IMAGE . $function_info['image'])) {
$data['thumb'] = $this->model_tool_image->resize($function_info['image'], 100, 100);
} else {
$data['thumb'] = $this->model_tool_image->resize('no_image.png', 100, 100);
}
$data['placeholder'] = $this->model_tool_image->resize('no_image.png', 100, 100);
if (isset($this->request->post['function_group_id'])) {
$data['function_group_id'] = $this->request->post['function_group_id'];
} elseif (!empty($function_info)) {
$data['function_group_id'] = $function_info['function_group_id'];
} else {
$data['function_group_id'] = '';
}
$this->load->model('catalog/function_group');
$data['function_groups'] = $this->model_catalog_function_group->getFunctionGroups();
if (isset($this->request->post['sort_order'])) {
$data['sort_order'] = $this->request->post['sort_order'];
} elseif (!empty($function_info)) {
$data['sort_order'] = $function_info['sort_order'];
} else {
$data['sort_order'] = '';
}
$data['header'] = $this->load->controller('common/header');
$data['column_left'] = $this->load->controller('common/column_left');
$data['footer'] = $this->load->controller('common/footer');
$this->response->setOutput($this->load->view('catalog/function_form', $data));
}
protected function validateForm() {
if (!$this->user->hasPermission('modify', 'catalog/function')) {
$this->error['warning'] = $this->language->get('error_permission');
}
if (!$this->request->post['function_group_id']) {
$this->error['function_group'] = $this->language->get('error_function_group');
}
foreach ($this->request->post['function_description'] as $language_id => $value) {
if ((utf8_strlen($value['name']) < 3) || (utf8_strlen($value['name']) > 64)) {
$this->error['name'][$language_id] = $this->language->get('error_name');
}
if ((utf8_strlen($value['description']) < 3) || (utf8_strlen($value['description']) > 1000)) {
$this->error['description'][$language_id] = $this->language->get('error_description');
}
}
return !$this->error;
}
protected function validateDelete() {
if (!$this->user->hasPermission('modify', 'catalog/function')) {
$this->error['warning'] = $this->language->get('error_permission');
}
$this->load->model('catalog/product');
foreach ($this->request->post['selected'] as $function_id) {
$product_total = $this->model_catalog_product->getTotalProductsByFunctionId($function_id);
if ($product_total) {
$this->error['warning'] = sprintf($this->language->get('error_product'), $product_total);
}
}
return !$this->error;
}
public function autocomplete() {
$json = array();
if (isset($this->request->get['filter_name'])) {
$this->load->model('catalog/function');
$filter_data = array(
'filter_name' => $this->request->get['filter_name'],
'start' => 0,
'limit' => 5
);
$results = $this->model_catalog_function->getFunctions($filter_data);
foreach ($results as $result) {
$json[] = array(
'function_id' => $result['function_id'],
'name' => strip_tags(html_entity_decode($result['name'], ENT_QUOTES, 'UTF-8')),
'function_group' => $result['function_group']
);
}
}
$sort_order = array();
foreach ($json as $key => $value) {
$sort_order[$key] = $value['name'];
}
array_multisort($sort_order, SORT_ASC, $json);
$this->response->addHeader('Content-Type: application/json');
$this->response->setOutput(json_encode($json));
}
}
Как и в случае с Моделью, Контроллер выглядит устрашающим только при первом взгляде. Он насыщен стандартными конструкциями, такими как хлебные крошки, обработка методов POST, GET значений переменных и пр. У нас присутствуют следующие Методы:
index()
(совмещен с getList()
) – обрабатывается при запросе admin/index.php?route=catalog/function
и возвращает список всех имеющихся Функций. Поддерживает фильтр через GET запросы. Поддерживает Пагинацию, если вы в Моделе указали все Методы;
add()
(совмещен с getForm()
и validateForm()
) – создает новую Функцию после успешного выполнение метода validateForm()
;
edit()
(совмещен с getForm()
и validateForm()
) – изменяет (обновляет) новую Функцию после успешного выполнение метода validateForm()
;
delete()
(совмещен с getList()
и validateForm()
) – удаляет заданную Функцию из списка Функций. Как уже было сказано, тут должна быть еще валидация на привязку Функции к Категории товаров, но она не написана, подсмотреть можно у контроллера Атрибутов;
getList()
— вынесенный отдельно Метод для работы со списком Функций. Т.к. компоненты Метода используются в нескольких других Методах, было вполне уместно создать отдельный Метод вместо избыточного кода;
getForm()
– аналогично getList()
, но уже для работы с формой добавления и удаления Функции;
validateForm()
– достаточно обычный валидатор формы в OpenCart. Проверяет, что у Пользователя есть права на изменения этого Контроллера, проверяет длину полей у Функции;
validateDelete()
– не дописанный и не используемый Метод валидации при удалении Функции. Должен проверять права Пользователя и что удаляемая Функция не имеет привязки к Категории товаров. Теоретически, можно написать Метод в Модели, который будет удалять привязку Функций к Категориям. Именно поэтому мы не храним список Функций и Групп Функций в каком-нибудь формате json, а каждую привязку отдельно в своей таблице.
autocomplete()
– не используемая функция для фильтра в списке Функций. Можете дописать на свое усмотрение.
Листинг function_group.php
<?php class ControllerCatalogFunctionGroup extends Controller { private $error = array(); public function index() { $this->load->language('catalog/function_group');
$this->document->setTitle($this->language->get('heading_title'));
$this->load->model('catalog/function_group');
$this->getList();
}
public function add() {
$this->load->language('catalog/function_group');
$this->document->setTitle($this->language->get('heading_title'));
$this->load->model('catalog/function_group');
if (($this->request->server['REQUEST_METHOD'] == 'POST') && $this->validateForm()) {
$this->model_catalog_function_group->addFunctionGroup($this->request->post);
$this->session->data['success'] = $this->language->get('text_success');
$url = '';
if (isset($this->request->get['sort'])) {
$url .= '&sort=' . $this->request->get['sort'];
}
if (isset($this->request->get['order'])) {
$url .= '&order=' . $this->request->get['order'];
}
if (isset($this->request->get['page'])) {
$url .= '&page=' . $this->request->get['page'];
}
$this->response->redirect($this->url->link('catalog/function_group', 'token=' . $this->session->data['token'] . $url, true));
}
$this->getForm();
}
public function edit() {
$this->load->language('catalog/function_group');
$this->document->setTitle($this->language->get('heading_title'));
$this->load->model('catalog/function_group');
if (($this->request->server['REQUEST_METHOD'] == 'POST') && $this->validateForm()) {
$this->model_catalog_function_group->editFunctionGroup($this->request->get['function_group_id'], $this->request->post);
$this->session->data['success'] = $this->language->get('text_success');
$url = '';
if (isset($this->request->get['sort'])) {
$url .= '&sort=' . $this->request->get['sort'];
}
if (isset($this->request->get['order'])) {
$url .= '&order=' . $this->request->get['order'];
}
if (isset($this->request->get['page'])) {
$url .= '&page=' . $this->request->get['page'];
}
$this->response->redirect($this->url->link('catalog/function_group', 'token=' . $this->session->data['token'] . $url, true));
}
$this->getForm();
}
public function delete() {
$this->load->language('catalog/function_group');
$this->document->setTitle($this->language->get('heading_title'));
$this->load->model('catalog/function_group');
if (isset($this->request->post['selected']) && $this->validateDelete()) {
foreach ($this->request->post['selected'] as $function_group_id) {
$this->model_catalog_function_group->deleteFunctionGroup($function_group_id);
}
$this->session->data['success'] = $this->language->get('text_success');
$url = '';
if (isset($this->request->get['sort'])) {
$url .= '&sort=' . $this->request->get['sort'];
}
if (isset($this->request->get['order'])) {
$url .= '&order=' . $this->request->get['order'];
}
if (isset($this->request->get['page'])) {
$url .= '&page=' . $this->request->get['page'];
}
$this->response->redirect($this->url->link('catalog/function_group', 'token=' . $this->session->data['token'] . $url, true));
}
$this->getList();
}
protected function getList() {
if (isset($this->request->get['sort'])) {
$sort = $this->request->get['sort'];
} else {
$sort = 'agd.name';
}
if (isset($this->request->get['order'])) {
$order = $this->request->get['order'];
} else {
$order = 'ASC';
}
if (isset($this->request->get['page'])) {
$page = $this->request->get['page'];
} else {
$page = 1;
}
$url = '';
if (isset($this->request->get['sort'])) {
$url .= '&sort=' . $this->request->get['sort'];
}
if (isset($this->request->get['order'])) {
$url .= '&order=' . $this->request->get['order'];
}
if (isset($this->request->get['page'])) {
$url .= '&page=' . $this->request->get['page'];
}
$data['breadcrumbs'] = array();
$data['breadcrumbs'][] = array(
'text' => $this->language->get('text_home'),
'href' => $this->url->link('common/dashboard', 'token=' . $this->session->data['token'], true)
);
$data['breadcrumbs'][] = array(
'text' => $this->language->get('heading_title'),
'href' => $this->url->link('catalog/function_group', 'token=' . $this->session->data['token'] . $url, true)
);
$data['add'] = $this->url->link('catalog/function_group/add', 'token=' . $this->session->data['token'] . $url, true);
$data['delete'] = $this->url->link('catalog/function_group/delete', 'token=' . $this->session->data['token'] . $url, true);
$data['function_groups'] = array();
$filter_data = array(
'sort' => $sort,
'order' => $order,
'start' => ($page - 1) * $this->config->get('config_limit_admin'),
'limit' => $this->config->get('config_limit_admin')
);
$function_group_total = $this->model_catalog_function_group->getTotalFunctionGroups();
$results = $this->model_catalog_function_group->getFunctionGroups($filter_data);
foreach ($results as $result) {
$data['function_groups'][] = array(
'function_group_id' => $result['function_group_id'],
'name' => $result['name'],
'sort_order' => $result['sort_order'],
'edit' => $this->url->link('catalog/function_group/edit', 'token=' . $this->session->data['token'] . '&function_group_id=' . $result['function_group_id'] . $url, true)
);
}
$data['heading_title'] = $this->language->get('heading_title');
$data['text_list'] = $this->language->get('text_list');
$data['text_no_results'] = $this->language->get('text_no_results');
$data['text_confirm'] = $this->language->get('text_confirm');
$data['column_name'] = $this->language->get('column_name');
$data['column_sort_order'] = $this->language->get('column_sort_order');
$data['column_action'] = $this->language->get('column_action');
$data['button_add'] = $this->language->get('button_add');
$data['button_edit'] = $this->language->get('button_edit');
$data['button_delete'] = $this->language->get('button_delete');
if (isset($this->error['warning'])) {
$data['error_warning'] = $this->error['warning'];
} else {
$data['error_warning'] = '';
}
if (isset($this->session->data['success'])) {
$data['success'] = $this->session->data['success'];
unset($this->session->data['success']);
} else {
$data['success'] = '';
}
if (isset($this->request->post['selected'])) {
$data['selected'] = (array)$this->request->post['selected'];
} else {
$data['selected'] = array();
}
$url = '';
if ($order == 'ASC') {
$url .= '&order=DESC';
} else {
$url .= '&order=ASC';
}
if (isset($this->request->get['page'])) {
$url .= '&page=' . $this->request->get['page'];
}
$data['sort_name'] = $this->url->link('catalog/function_group', 'token=' . $this->session->data['token'] . '&sort=agd.name' . $url, true);
$data['sort_sort_order'] = $this->url->link('catalog/function_group', 'token=' . $this->session->data['token'] . '&sort=ag.sort_order' . $url, true);
$url = '';
if (isset($this->request->get['sort'])) {
$url .= '&sort=' . $this->request->get['sort'];
}
if (isset($this->request->get['order'])) {
$url .= '&order=' . $this->request->get['order'];
}
$pagination = new Pagination();
$pagination->total = $function_group_total;
$pagination->page = $page;
$pagination->limit = $this->config->get('config_limit_admin');
$pagination->url = $this->url->link('catalog/function_group', 'token=' . $this->session->data['token'] . $url . '&page={page}', true);
$data['pagination'] = $pagination->render();
$data['results'] = sprintf($this->language->get('text_pagination'), ($function_group_total) ? (($page - 1) * $this->config->get('config_limit_admin')) + 1 : 0, ((($page - 1) * $this->config->get('config_limit_admin')) > ($function_group_total - $this->config->get('config_limit_admin'))) ? $function_group_total : ((($page - 1) * $this->config->get('config_limit_admin')) + $this->config->get('config_limit_admin')), $function_group_total, ceil($function_group_total / $this->config->get('config_limit_admin')));
$data['sort'] = $sort;
$data['order'] = $order;
$data['header'] = $this->load->controller('common/header');
$data['column_left'] = $this->load->controller('common/column_left');
$data['footer'] = $this->load->controller('common/footer');
$this->response->setOutput($this->load->view('catalog/function_group_list', $data));
}
protected function getForm() {
$data['heading_title'] = $this->language->get('heading_title');
$data['text_form'] = !isset($this->request->get['function_group_id']) ? $this->language->get('text_add') : $this->language->get('text_edit');
$data['entry_name'] = $this->language->get('entry_name');
$data['entry_sort_order'] = $this->language->get('entry_sort_order');
$data['button_save'] = $this->language->get('button_save');
$data['button_cancel'] = $this->language->get('button_cancel');
if (isset($this->error['warning'])) {
$data['error_warning'] = $this->error['warning'];
} else {
$data['error_warning'] = '';
}
if (isset($this->error['name'])) {
$data['error_name'] = $this->error['name'];
} else {
$data['error_name'] = array();
}
$url = '';
if (isset($this->request->get['sort'])) {
$url .= '&sort=' . $this->request->get['sort'];
}
if (isset($this->request->get['order'])) {
$url .= '&order=' . $this->request->get['order'];
}
if (isset($this->request->get['page'])) {
$url .= '&page=' . $this->request->get['page'];
}
$data['breadcrumbs'] = array();
$data['breadcrumbs'][] = array(
'text' => $this->language->get('text_home'),
'href' => $this->url->link('common/dashboard', 'token=' . $this->session->data['token'], true)
);
$data['breadcrumbs'][] = array(
'text' => $this->language->get('heading_title'),
'href' => $this->url->link('catalog/function_group', 'token=' . $this->session->data['token'] . $url, true)
);
if (!isset($this->request->get['function_group_id'])) {
$data['action'] = $this->url->link('catalog/function_group/add', 'token=' . $this->session->data['token'] . $url, true);
} else {
$data['action'] = $this->url->link('catalog/function_group/edit', 'token=' . $this->session->data['token'] . '&function_group_id=' . $this->request->get['function_group_id'] . $url, true);
}
$data['cancel'] = $this->url->link('catalog/function_group', 'token=' . $this->session->data['token'] . $url, true);
if (isset($this->request->get['function_group_id']) && ($this->request->server['REQUEST_METHOD'] != 'POST')) {
$function_group_info = $this->model_catalog_function_group->getFunctionGroup($this->request->get['function_group_id']);
}
$this->load->model('localisation/language');
$data['languages'] = $this->model_localisation_language->getLanguages();
if (isset($this->request->post['function_group_description'])) {
$data['function_group_description'] = $this->request->post['function_group_description'];
} elseif (isset($this->request->get['function_group_id'])) {
$data['function_group_description'] = $this->model_catalog_function_group->getFunctionGroupDescriptions($this->request->get['function_group_id']);
} else {
$data['function_group_description'] = array();
}
if (isset($this->request->post['sort_order'])) {
$data['sort_order'] = $this->request->post['sort_order'];
} elseif (!empty($function_group_info)) {
$data['sort_order'] = $function_group_info['sort_order'];
} else {
$data['sort_order'] = '';
}
$data['header'] = $this->load->controller('common/header');
$data['column_left'] = $this->load->controller('common/column_left');
$data['footer'] = $this->load->controller('common/footer');
$this->response->setOutput($this->load->view('catalog/function_group_form', $data));
}
protected function validateForm() {
if (!$this->user->hasPermission('modify', 'catalog/function_group')) {
$this->error['warning'] = $this->language->get('error_permission');
}
foreach ($this->request->post['function_group_description'] as $language_id => $value) {
if ((utf8_strlen($value['name']) < 3) || (utf8_strlen($value['name']) > 64)) {
$this->error['name'][$language_id] = $this->language->get('error_name');
}
}
return !$this->error;
}
protected function validateDelete() {
if (!$this->user->hasPermission('modify', 'catalog/function_group')) {
$this->error['warning'] = $this->language->get('error_permission');
}
$this->load->model('catalog/function');
foreach ($this->request->post['selected'] as $function_group_id) {
$function_total = $this->model_catalog_function->getTotalFunctionsByFunctionGroupId($function_group_id);
if ($function_total) {
$this->error['warning'] = sprintf($this->language->get('error_function'), $function_total);
}
}
return !$this->error;
}
}
Методы этого Контроллера очень похожи на function.php
, поэтому не вижу особого смысла останавливаться подробно на каждом из них.
Нам осталось написать всего чуть-чуть:
- Представления для Функций и Групп Функций
- Добавить ссылки на Контроллеры в главное меню админки
- Дать разрешения Пользователю на просмотр и редактирование новых Контроллеров
Терпение и Вы увидите, какая красота у нас получилась!
Т.к. у нас у обоих контроллеров есть Методы отвечающие как за списки так и за формы, то и Представлений получается 4 шт. Создадим в admin/view/template/catalog/
файлы:
function_form.tpl
;
function_list.tpl
;
function_group_form.tpl
;
function_group_list.tpl
.
Листинги файлов будут в этом архиве. Ничего особенно в них нет, поэтому можно просто скопировать в папку.
Добавим вывод нашего нового Модуля в главное меню и добавим текущему Пользователю права на просмотр и редактирование новых Контроллеров.
Идем в admin/controller/common/column_left.php
и скажем после:
// Attributes
$attribute = array();
if ($this->user->hasPermission('access', 'catalog/attribute')) {
$attribute[] = array(
'name' => $this->language->get('text_attribute'),
'href' => $this->url->link('catalog/attribute', 'token=' . $this->session->data['token'], true),
'children' => array()
);
}
if ($this->user->hasPermission('access', 'catalog/attribute_group')) {
$attribute[] = array(
'name' => $this->language->get('text_attribute_group'),
'href' => $this->url->link('catalog/attribute_group', 'token=' . $this->session->data['token'], true),
'children' => array()
);
}
if ($attribute) {
$catalog[] = array(
'name' => $this->language->get('text_attribute'),
'href' => '',
'children' => $attribute
);
}
Добавляем:
// Function Daikin
$function = array();
if ($this->user->hasPermission('access', 'catalog/function_group')) {
$function[] = array(
'name' => $this->language->get('text_function'),
'href' => $this->url->link('catalog/function', 'token=' . $this->session->data['token'], true),
'children' => array()
);
}
if ($this->user->hasPermission('access', 'catalog/function_group')) {
$function[] = array(
'name' => $this->language->get('text_function_group'),
'href' => $this->url->link('catalog/function_group', 'token=' . $this->session->data['token'], true),
'children' => array()
);
}
if ($function) {
$catalog[] = array(
'name' => $this->language->get('text_function'),
'href' => '',
'children' => $function
);
}
Ну и выставим права нашему Пользователю, для этого идем в Админке в Система -> Группы пользователей. Находим вашу группу (обычно, это Administrator) и добавляем права на Контроллеры: catalog/function
и catalog/function_group
Несколько скринов для оценки красоты:
Демо
https://oc.netsh.pp.ua/admin/
demo/demo
Архив со всеми файлами в правильной структуре + иконки: Custom_module
В следущей части статьи создадим привязку нашего модуля к Категориям и напишем пользовательскую часть модуля.