FIA Создание модуля для BroCart [OpenCart] 3 версии

ALarik

ALarik

expert
#1
Доброго времени, други и подруги!
Сегодня будем рассматривать обращение комрада, который предложил реализовать поиск картинок, используя API от Google, отписав заранее задачи, в стиле ТЗ со скриншотами и описанием, а так же ссылкой на аналогичный модуль, но под версию 2.3. Так же, написанием статьи послужило общение с товарищем, который желает обрести знание в шоп-мейкерстве, частью которого и будет создание модуля по образу и подобию, вернее будет рассмотрено создание каркаса, сама же магия алхимии будет раскрыта в зависимости от задачи и отмашки главнокомандующего, по чьей просьбе я так же создаю эту тему, что бы и другие смогли почитать и обучится.

Цель: Создание модуля для bro 3 версии
Задача модуля: парсинг картинок, используя Google API
Инструменты: прямые руки, хорошее настроение, пол литра киндзмараули
Поехали... :cool:

Как создать модуль для BroCart [OpenCart] 3
  • создание каркаса MVC для админки
  • создание каркаса MVC для витрины (если необходимо)
  • создание установочного файла
  • реализация функционала
STEP 0: Название файлов модуля
Символьное обозначение файлов должно соответствовать базовой функциональности, в нижнем регистре, если есть 2 и более слова, разделяем нижним подчеркиванием
У меня получилось вот такое название: brogsi (BROcart Google Search Images), тоесть все файлы, я буду называть либо brogsi.php либо brogsi.twig либо brogsi.tpl
Согласно паттерну MVC + L:

mvcl-png.68 - opencart

Все файлы нам нужно создать согласно их размещению в каталоге.
Начнем с раздела admin:
0.1 Первым делом я начинаю с контроллера (на картинке он обозначен в центре, тоесть по адресу: admin\controller\extension\module\ я добавляю новый файл, под названием brogsi.php
Настройки файла, я буду хранить в базе и файл модели (на рисунке синий круг - справа) я создавать не буду, а создам только файл с переменными (нижний желтый круг на рисунке) для разных языков:
0.2. В бро на данный момент 3 (языка), соответственно в каждом из каталогов:
  • admin\language\en-gb\extension\module\
  • admin\language\uk-ua\extension\module\
  • admin\language\ru-ru\extension\module\
и создаем по файлу brogsi.php с базовыми текстовыми переменными:
Для англ. языка:
PHP:
<?php
// Heading
$_['heading_title']    = 'BROcart Google Search Images [brogsi]';

// Text
$_['text_extension']   = 'Extensions';
$_['text_success']     = 'Success: You have modified brogsi module!';
$_['text_edit']        = 'Edit Brogsi Module';

// Entry
$_['entry_api']        = 'API key';
$_['entry_type']       = 'ImgType';
$_['entry_size']       = 'ImgSize';
$_['entry_color']      = 'ImgColorType';
$_['entry_status']     = 'Status';

// Error
$_['error_permission'] = 'Warning: You do not have permission to modify brogsi module!';
$_['error_api']        = 'API key required!';
Для укр. файла:
PHP:
<?php
// Heading
$_['heading_title']    = 'BROcart Google Search Images [brogsi]';

// Text
$_['text_extension']   = 'Доповнення';
$_['text_success']     = 'Вітання: Ви щойно змінили налаштування в модулі!';
$_['text_edit']        = 'Редагувати модуль';

// Entry
$_['entry_api']        = 'API ключ';
$_['entry_type']       = 'Тип малюнку';
$_['entry_size']       = 'Розмір малюнку';
$_['entry_color']      = 'Тип кольору';
$_['entry_status']     = 'Статус';

// Error
$_['error_permission'] = 'Увага: У Вас немає прав для налаштування модуля!';
$_['error_api']        = 'Увага: API ключ є обов\'язковим!';
Ну и для рос. файла:
PHP:
<?php
// Heading
$_['heading_title']    = 'BROcart Google Search Images [brogsi]';

// Text
$_['text_extension']   = 'Расширения';
$_['text_success']     = 'Поздравляем: Настройки успешно изменены!';
$_['text_edit']        = 'Редактировать модуль';

// Entry
$_['entry_api']        = 'API ключ';
$_['entry_type']       = 'Тип рисунка';
$_['entry_size']       = 'Размер рисунка';
$_['entry_color']      = 'Тип цвета';
$_['entry_status']     = 'Статус';

// Error
$_['error_permission'] = 'Внимание: У Вас нет прав для настроек модуля!';
$_['error_api']        = 'Внимание: API ключ обязателен!';
Теперь осталось добавить файл для отображения (на рисунке MVC это верхний - зелёный круг) в каталоге:
admin\view\template\extension\module\ файл под названием brogsi.twig (так как в брокарте, есть поддержка TPL без конфликтов, то я создам brogsi.tpl).
Готово, файловый каркас по паттерну MVC готов, осталось теперь реализовать функционал, языковые переменные мы заполнили с самого начала, осталось только контроллер и view файл заполнить кодом\разметкой, ну что ж... поехали ;)
STEP: 1 Файл-контроллер
Напомню, что находится он в разделе admin\controller\extension\module\brogsi.php
Открываем его любимым редактором кода, у каждого он свой, я же предпочту своим, и добавляем первые строки:
PHP:
<?php
class ControllerExtensionModuleBrogsi extends Controller {
    private $error = array();

    public function index() {

        $this->load->language('extension/module/brogsi');

        $this->document->setTitle($this->language->get('heading_title'));
   
        $this->load->model('setting/setting');
   
        if (($this->request->server['REQUEST_METHOD'] == 'POST') && $this->validate()) {
            $this->model_setting_setting->editSetting('module_brogsi', $this->request->post);
            $this->session->data['success'] = $this->language->get('text_success');
            $this->response->redirect($this->url->link('marketplace/extension', 'user_token=' . $this->session->data['user_token'] . '&type=module', true));
        }
   
        if (isset($this->error['warning'])) {
            $data['error_warning'] = $this->error['warning'];
        } else {
            $data['error_warning'] = '';
        }
   
        $data['breadcrumbs'] = array();

        $data['breadcrumbs'][] = array(
            'text' => $this->language->get('text_home'),
            'href' => $this->url->link('common/dashboard', 'user_token=' . $this->session->data['user_token'], true)
        );

        $data['breadcrumbs'][] = array(
            'text' => $this->language->get('text_extension'),
            'href' => $this->url->link('marketplace/extension', 'user_token=' . $this->session->data['user_token'] . '&type=module', true)
        );
   
        $data['breadcrumbs'][] = array(
            'text' => $this->language->get('heading_title'),
            'href' => $this->url->link('extension/module/brogsi', 'user_token=' . $this->session->data['user_token'], true)
        );
   
        $data['action'] = $this->url->link('extension/module/brogsi', 'user_token=' . $this->session->data['user_token'], true);  

        $data['cancel'] = $this->url->link('marketplace/extension', 'user_token=' . $this->session->data['user_token'] . '&type=module', true);
   
        if (isset($this->request->post['module_brogsi_api'])) {
            $data['module_brogsi_api'] = $this->request->post['module_brogsi_api'];  
        } else {
            $data['module_brogsi_api'] = $this->config->get('module_brogsi_api');
        }
   
        if (isset($this->request->post['module_brogsi_type'])) {
            $data['module_brogsi_type'] = $this->request->post['module_brogsi_type'];
        } else {
            $data['module_brogsi_type'] = $this->config->get('module_brogsi_type');
        }
   
        $data['file_types'] = array(
            'clipart',
            'face',
            'lineart',
            'news',
            'photo'
        );
   
        if (isset($this->request->post['module_brogsi_size'])) {
            $data['module_brogsi_size'] = $this->request->post['module_brogsi_size'];
        } else {
            $data['module_brogsi_size'] = $this->config->get('module_brogsi_size');
        }
   
        $data['file_sizes'] = array(
            'icon',
            'small',
            'medium',
            'large',
            'xlarge',
            'xxlarge',
            'huge'
        );
   
        if (isset($this->request->post['module_brogsi_color'])) {
            $data['module_brogsi_color'] = $this->request->post['module_brogsi_color'];
        } else {
            $data['module_brogsi_color'] = $this->config->get('module_brogsi_color');
        }
   
        $data['file_colors'] = array(
            'mono',
            'gray',
            'color'
        );
   
        if (isset($this->request->post['module_brogsi_status'])) {
            $data['module_brogsi_status'] = $this->request->post['module_brogsi_status'];
        } else {
            $data['module_brogsi_status'] = $this->config->get('module_brogsi_status');
        }

        $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('extension/module/brogsi', $data));
    }

    protected function validate() {
        if (!$this->user->hasPermission('modify', 'extension/module/brogsi')) {
            $this->error['warning'] = $this->language->get('error_permission');
        }
        if (empty($this->request->post['module_brogsi_api'])) {
            $this->error['warning'] = $this->language->get('error_api');
        }
        return !$this->error;
    }
}
?>

Ну и код для самого view файла:
HTML:
<?php echo $header; ?><?php echo $column_left; ?>
<div id="content">
    <div class="page-header">
        <div class="container-fluid">
          <div class="pull-right">
            <button type="submit" form="form-category" data-toggle="tooltip" title="<?php echo $button_save; ?>" class="btn btn-primary"><i class="fa fa-save"></i></button>
            <a href="<?php echo $cancel; ?>" data-toggle="tooltip" title="<?php echo $button_cancel; ?>" class="btn btn-default"><i class="fa fa-reply"></i></a></div>
          <h1><?php echo $heading_title; ?></h1>
          <ul class="breadcrumb">
            <?php foreach ($breadcrumbs as $breadcrumb) { ?>
            <li><a href="<?php echo $breadcrumb['href']; ?>"><?php echo $breadcrumb['text']; ?></a></li>
            <?php } ?>
          </ul>
        </div>
    </div>
    <div class="container-fluid">
        <?php if ($error_warning) { ?>
        <div class="alert alert-danger"><i class="fa fa-exclamation-circle"></i> <?php echo $error_warning; ?>
          <button type="button" class="close" data-dismiss="alert">&times;</button>
        </div>
        <?php } ?>
        <div class="panel panel-default">
            <div class="panel-heading">
                <h3 class="panel-title"><i class="fa fa-pencil"></i> <?php echo $text_edit; ?></h3>
            </div>
            <div class="panel-body">
                <form action="<?php echo $action; ?>" method="post" enctype="multipart/form-data" id="form-category" class="form-horizontal">
                    <div class="form-group">
                        <label class="col-sm-2 control-label" for="input-api"><?php echo $entry_api; ?></label>
                        <div class="col-sm-10">          
                            <input type="text" name="module_brogsi_api" value="<?php echo $module_brogsi_api; ?>" placeholder="<?php echo $entry_api; ?>" id="input-api" class="form-control" />              
                        </div>
                    </div>    
                    <div class="form-group">
                        <label class="col-sm-2 control-label" for="input-type"><?php echo $entry_type; ?></label>
                        <div class="col-sm-10">
                          <select name="module_brogsi_type" id="input-type" class="form-control">
                            <option value="0"><?php echo $text_none; ?></option>
                            <?php foreach ($file_types as $file_type) { ?>
                            <?php if ($file_type == $module_brogsi_type) { ?>
                            <option value="<?php echo $file_type; ?>" selected="selected"><?php echo $file_type; ?></option>
                            <?php } else { ?>
                            <option value="<?php echo $file_type; ?>"><?php echo $file_type; ?></option>
                            <?php } ?>
                            <?php } ?>
                          </select>
                        </div>
                    </div>    
                    <div class="form-group">
                        <label class="col-sm-2 control-label" for="input-size"><?php echo $entry_size; ?></label>
                        <div class="col-sm-10">
                          <select name="module_brogsi_size" id="input-size" class="form-control">
                            <option value="0"><?php echo $text_none; ?></option>
                            <?php foreach ($file_sizes as $file_size) { ?>
                            <?php if ($file_size == $module_brogsi_size) { ?>
                            <option value="<?php echo $file_size; ?>" selected="selected"><?php echo $file_size; ?></option>
                            <?php } else { ?>
                            <option value="<?php echo $file_size; ?>"><?php echo $file_size; ?></option>
                            <?php } ?>
                            <?php } ?>
                          </select>
                        </div>
                    </div>    
                    <div class="form-group">
                        <label class="col-sm-2 control-label" for="input-color"><?php echo $entry_color; ?></label>
                        <div class="col-sm-10">
                          <select name="module_brogsi_color" id="input-color" class="form-control">
                            <option value="0"><?php echo $text_none; ?></option>
                            <?php foreach ($file_colors as $file_color) { ?>
                            <?php if ($file_color == $module_brogsi_color) { ?>
                            <option value="<?php echo $file_color; ?>" selected="selected"><?php echo $file_color; ?></option>
                            <?php } else { ?>
                            <option value="<?php echo $file_color; ?>"><?php echo $file_color; ?></option>
                            <?php } ?>
                            <?php } ?>
                          </select>
                        </div>
                    </div>
                    <div class="form-group">
                        <label class="col-sm-2 control-label" for="input-status"><?php echo $entry_status; ?></label>
                        <div class="col-sm-10">
                            <select name="module_brogsi_status" id="input-status" class="form-control">
                                <?php if ($module_brogsi_status) { ?>
                                <option value="1" selected="selected"><?php echo $text_enabled; ?></option>
                                <option value="0"><?php echo $text_disabled; ?></option>
                                <?php } else { ?>
                                <option value="1"><?php echo $text_enabled; ?></option>
                                <option value="0" selected="selected"><?php echo $text_disabled; ?></option>
                                <?php } ?>
                            </select>
                        </div>
                    </div>
                </form>
            </div>
        </div>
    </div>
</div>
<?php echo $footer; ?>

В результате чего, получаем вот такой вот простенький модуль:
brogsi-jpg.69 - opencart


где у нас под номером 1 - левая колонка, подключенная в контроллере:
PHP:
$data['column_left'] = $this->load->controller('common/column_left');
номер 2 соответственно - шапка сайта и подвал (котороне нет на рисунке, но по факту он есть):
PHP:
$data['header'] = $this->load->controller('common/header');

$data['footer'] = $this->load->controller('common/footer');
далее, под номером 3 выводит заголовок <h1>, которого нет в контроллере, но он автоматически взят из файла языка heading_title
номер 4 - хлебные крошки, массив которых в контроллере, мы указали вот так:
PHP:
        $data['breadcrumbs'] = array();

        $data['breadcrumbs'][] = array(
            'text' => $this->language->get('text_home'),
            'href' => $this->url->link('common/dashboard', 'user_token=' . $this->session->data['user_token'], true)
        );

        $data['breadcrumbs'][] = array(
            'text' => $this->language->get('text_extension'),
            'href' => $this->url->link('marketplace/extension', 'user_token=' . $this->session->data['user_token'] . '&type=module', true)
        );
   
        $data['breadcrumbs'][] = array(
            'text' => $this->language->get('heading_title'),
            'href' => $this->url->link('extension/module/brogsi', 'user_token=' . $this->session->data['user_token'], true)
        );
под номером 5 - выводится ошибка, которую нужно указать, если какое то из полей обязательно к заполнению и, что вполне логично, без которого наш модуль не сможет корректно парсить картинки. В контроллере, мы инициализировали переменную с ошибкой сразу в начале класса (перед функцией index):
private $error = array();
далее логика вывода в самом контроллере:
PHP:
        if (isset($this->error['warning'])) {
            $data['error_warning'] = $this->error['warning'];
        } else {
            $data['error_warning'] = '';
        }
а добавляется она в момент проверки данных, когда происходит нажатие на иконку "сохранить" в методе $this->validate():
PHP:
        if (empty($this->request->post['module_brogsi_api'])) {
            $this->error['warning'] = $this->language->get('error_api');
        }
ну и выводится она во view файле в переменной:
HTML:
    <?php if ($error_warning) { ?>
        <div class="alert alert-danger"><i class="fa fa-exclamation-circle"></i> <?php echo $error_warning; ?>
          <button type="button" class="close" data-dismiss="alert">&times;</button>
        </div>
    <?php } ?>
далее, под номером 6 у нас выводит само поле, для ввода ключа API, которое мы указали в контроллере:
PHP:
        if (isset($this->request->post['module_brogsi_api'])) {
            $data['module_brogsi_api'] = $this->request->post['module_brogsi_api'];  
        } else {
            $data['module_brogsi_api'] = $this->config->get('module_brogsi_api');
        }
под номером 7 у нас указаный статичный массив параметров, который мы взяли отсюда и в контроллере указали его так:
PHP:
        $data['file_colors'] = array(
            'mono',
            'gray',
            'color'
        );
ну и наконец, под 8-м номером у нас сам статус модуля, который может быть показан так же и на странице со списком модулей:
PHP:
        if (isset($this->request->post['module_brogsi_status'])) {
            $data['module_brogsi_status'] = $this->request->post['module_brogsi_status'];
        } else {
            $data['module_brogsi_status'] = $this->config->get('module_brogsi_status');
        }
Вот так вот создан модуль (простенький и с минимумом функционала, сохраняет базовые параметры в настройки и может быть получен в любом месте сайта, архив с файлами приложу к скачиванию, надеюсь понравилось, остальную магию по парсеру раскрою, еслим получу фидбэк от пользователей, что материал понравился и есть желание продолжить вникать в азы кодинга под бро или опенкарт, всем приятного вечера booboo
Enjoy, bro!
 

Вкладення

  • brogsi.zip (6.8 КБ)
    File size
    6.8 КБ
    Download
    38
Останнє редагування модератором:
Baco

Baco

architect
#2
Хороший мануал, спасибо, давно не встречал такого детального описания, хотелось бы отдельно узнать, как данные попадают из базы - в контроллер, а из него - во view файл, как передаются обратно, если можно на примере этого же простенького модуля, расписать от момента создания класса, зачем он имеет такое название, заканчивая:
PHP:
$this->response->setOutput($this->load->view('extension/module/brogsi', $data));
С описанием, что это, как срабатывает, что выводит в итоге.
Так же приглашаю участников, кому интересно разобрать все моменты, которые были до сих пор непонятны, и вызывали отложить разработку на "когда-нибудь".
 
yura_co

yura_co

master
#3
прошу мне отдать локализацию 2-х языков
по настройкам прошу рассмотреть возможность указания нескольких апи у указанием лимита на каждый ключ, на тот случай если гугль изменит лимит на запросы или будут докуплены дополнительные запросы, ну и при достижении лимита переходим на следующий апи
ну и наверное я бы вынес содержимое масивов
file_sizes
file_types
file_color
вынес в языковые файлы
 
Останнє редагування:
OP
ALarik

ALarik

expert
Thread Starter #4
по нескольким апи - понял, помудрую, касательно выноса параметров в языковые переменные, то разве дополнив, например вот так:
PHP:
        $data['file_types'] = array(
            'clipart' => $this->language->get('text_clipart'),
            'face' => $this->language->get('text_face'),
            'lineart' => $this->language->get('text_lineart'),
            'news' => $this->language->get('text_news'),
            'photo' => $this->language->get('text_photo')
        );
далее, во въюшке, я переберу как ключ\значение, главное подобавлять параметры - в языковые файлы.
 
A

Abalab

Шопмейкер
#8
рад что понравилось, задавайте вопросы, попробуемс ответить, если вдруг чего не так поедет
Артур, как с вами можно связаться? на сайте не получается пройти полностью регистрацию. Хотим узнать стоимость разработке модуля. Не можем найти специалиста.
 
OP
ALarik

ALarik

expert
Thread Starter #9
как с вами можно связаться?
здравствуйте, можно по скайпу, можно в телеграме, или оставьте свои данные, я свяжусь с вами. но меня больше всего интересует момент с:
на сайте не получается пройти полностью регистрацию
напишите в 2-х словах, что не так, мы для пользователей стараемся, одновременно спамеров хотим отсеять.
 
N

nsklital

Шопмейкер
#10
Добрый день. очень хорошая статья . подскажите есть ли статья о написании модуля для опенкарт 3. rest api или api синоризации. А именно выгрузка товаров с опенкарт на площадки или на другой магазин. очень буду благодарен.
 
Baco

Baco

architect
#11
Доброго дня, саме по rest api потрібно деталізовано розуміти, під що саме писати, які методи потрібні, тощо. а щодо вивантажень, то оптимальніше за все, це зробити файл-фід, по типу як файл sitemap.xml, тільки розширений і видавати в такому файлі потрібну інформацію, а на іншому магазині - парсити це все.
 
N

nsklital

Шопмейкер
#12
Доброго дня, саме по rest api потрібно деталізовано розуміти, під що саме писати, які методи потрібні, тощо. а щодо вивантажень, то оптимальніше за все, це зробити файл-фід, по типу як файл sitemap.xml, тільки розширений і видавати в такому файлі потрібну інформацію, а на іншому магазині - парсити це все.
Нет не подходит фид. На том маркетплейсе своя документация api
 
Baco

Baco

architect
#17
Добрый день. очень хорошая статья . подскажите есть ли статья о написании модуля для опенкарт 3. rest api или api синоризации. А именно выгрузка товаров с опенкарт на площадки или на другой магазин. очень буду благодарен.
для конструктивної відповіді, потрібно дати приклад АРІ, під котру писати рішення, бо в у відносному %, різними будуть key->value, як такого, немає стандартизовано єдиного реєстру методів (action) та параметрів, як ключів, REST – це стиль архітектури АРІ для розподілених систем, правила взаємодії, так би мовити. ось наприклад instagram API, а ось стаття по REST API для розуміння.
 
Зверху