Сегодня мы будем готовить свой модуль для opencart с нуля. Такая необходимость часто возникает у разработчиков магазинов. Если сегодня это необходимость возникла и у вас - читаем далееСтруктура файлов и директорийДля начала нужно понимать, что opencart и его модули написаны по схеме mvc или даже mvcl. Для многих это понятная и привычная схема, но не для всех. Поэтому пару слов об mvc в opencartM - Model. Отвечает за работу с базой данных V - Viewer. Отвечает за отображение модуля конечному пользователю C - Controller. Отвечает за взаимодействие между моделью и вьювером L - Language. Отвечает за языки, для которых будет формироваться контентСледственно у нас должен появиться набор директорий для админки и фронтэнда:admin/controller/extension/moduleadmin/model/extension/module - не обязательно, если не предполагаются запросы к базе данныхadmin/language/ru-ru/extension/module - это для русского языка. Если нужны другие языки - нужно и для другихadmin/view/template/extension/modulecatalog/controller/extension/modulecatalog/model/extension/module - не обязательно, если не предполагаются запросы к базе данныхcatalog/language/ru-ru/extension/module- это для русского языка. Если нужны другие языки - нужно и для другихcatalog/view/theme/default/extension/moduleИтого 31 (Тридцать одну!) директорию нам нужно создать. И это только для иерархии.Неужели нет способа проще? Конечно же есть, но о нем позже. Нам же нужно понимать как устроен opencart и как он работает.Теперь определяемся с именем модуля и его назначением. Пусть это будет "Рекомендуемые плюс" (featuredplus). А делать он будет вот что:Рекомендовать товары покупателям в зависимости от категории, в которой они находятся.В каждую конечную директорию (module) для контроллера, модели и языка нужно поместить php файл, а в конечную директорию вьюверов - twig файл с именем нашего модуля. Таким образом у нас получится следующая структура:admin/controller/extension/module/featuredplus.phpadmin/language/ru-ru/extension/module/featuredplus.phpadmin/language/en-gb/extension/module/featuredplus.phpadmin/view/template/extension/module/featuredplus.twigcatalog/controller/extension/module/featuredplus.phpcatalog/language/ru-ru/extension/module/featuredplus.phpcatalog/language/en-gb/extension/module/featuredplus.phpcatalog/view/theme/default/extension/module/featuredplus.twigКто заметил, что нет файлов модели? Тот получает виртуальный леденец 😉Как же так? - Спросите вы. Мы разве не будем брать информацию о товарах из базы данных?Будем. Но наш модуль не будет обращаться к базе данных. Мы просто подключим в контроллере нашего модуля модель товаров opencart. А модель товаров в свою очередь берет информацию из базы данных.Теперь посмотрим на текст всех файловadmin/controller/extension/module/featuredplus.phpPHP <?php class ControllerExtensionModuleFeaturedPlus extends Controller { private $error = array(); public function index() { $this->load->language('extension/module/featuredplus'); $this->document->setTitle($this->language->get('heading_title')); $this->load->model('setting/module'); if (($this->request->server['REQUEST_METHOD'] == 'POST') && $this->validate()) { if (!isset($this->request->get['module_id'])) { $this->model_setting_module->addModule('featuredplus', $this->request->post); } else { $this->model_setting_module->editModule($this->request->get['module_id'], $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'] = ''; } if (isset($this->error['name'])) { $data['error_name'] = $this->error['name']; } else { $data['error_name'] = ''; } if (isset($this->error['width'])) { $data['error_width'] = $this->error['width']; } else { $data['error_width'] = ''; } if (isset($this->error['height'])) { $data['error_height'] = $this->error['height']; } else { $data['error_height'] = ''; } $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) ); if (!isset($this->request->get['module_id'])) { $data['breadcrumbs'][] = array( 'text' => $this->language->get('heading_title'), 'href' => $this->url->link('extension/module/featuredplus', 'user_token=' . $this->session->data['user_token'], true) ); } else { $data['breadcrumbs'][] = array( 'text' => $this->language->get('heading_title'), 'href' => $this->url->link('extension/module/featuredplus', 'user_token=' . $this->session->data['user_token'] . '&module_id=' . $this->request->get['module_id'], true) ); } if (!isset($this->request->get['module_id'])) { $data['action'] = $this->url->link('extension/module/featuredplus', 'user_token=' . $this->session->data['user_token'], true); } else { $data['action'] = $this->url->link('extension/module/featuredplus', 'user_token=' . $this->session->data['user_token'] . '&module_id=' . $this->request->get['module_id'], true); } $data['cancel'] = $this->url->link('marketplace/extension', 'user_token=' . $this->session->data['user_token'] . '&type=module', true); if (isset($this->request->get['module_id']) && ($this->request->server['REQUEST_METHOD'] != 'POST')) { $module_info = $this->model_setting_module->getModule($this->request->get['module_id']); } $data['user_token'] = $this->session->data['user_token']; if (isset($this->request->post['name'])) { $data['name'] = $this->request->post['name']; } elseif (!empty($module_info)) { $data['name'] = $module_info['name']; } else { $data['name'] = ''; } $this->load->model('catalog/product'); $data['products'] = array(); if (!empty($this->request->post['product'])) { $products = $this->request->post['product']; } elseif (!empty($module_info['product'])) { $products = $module_info['product']; } else { $products = array(); } $this->load->model('catalog/category'); if (isset($this->request->post['product_category'])) { $categories = $this->request->post['product_category']; } elseif (!empty($module_info['product_category'])) { $categories = $module_info['product_category']; } else { $categories = array(); } $data['product_categories'] = array(); foreach ($categories as $category_id) { $category_info = $this->model_catalog_category->getCategory($category_id); if ($category_info) { $data['product_categories'][] = array( 'category_id' => $category_info['category_id'], 'name' => ($category_info['path']) ? $category_info['path'] . ' > ' . $category_info['name'] : $category_info['name'] ); } } foreach ($products as $product_id) { $product_info = $this->model_catalog_product->getProduct($product_id); if ($product_info) { $data['products'][] = array( 'product_id' => $product_info['product_id'], 'name' => $product_info['name'] ); } } if (isset($this->request->post['limit'])) { $data['limit'] = $this->request->post['limit']; } elseif (!empty($module_info)) { $data['limit'] = $module_info['limit']; } else { $data['limit'] = 5; } if (isset($this->request->post['width'])) { $data['width'] = $this->request->post['width']; } elseif (!empty($module_info)) { $data['width'] = $module_info['width']; } else { $data['width'] = 200; } if (isset($this->request->post['height'])) { $data['height'] = $this->request->post['height']; } elseif (!empty($module_info)) { $data['height'] = $module_info['height']; } else { $data['height'] = 200; } if (isset($this->request->post['status'])) { $data['status'] = $this->request->post['status']; } elseif (!empty($module_info)) { $data['status'] = $module_info['status']; } else { $data['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/featuredplus', $data)); } protected function validate() { if (!$this->user->hasPermission('modify', 'extension/module/featuredplus')) { $this->error['warning'] = $this->language->get('error_permission'); } if ((utf8_strlen($this->request->post['name']) < 3) || (utf8_strlen($this->request->post['name']) > 64)) { $this->error['name'] = $this->language->get('error_name'); } if (!$this->request->post['width']) { $this->error['width'] = $this->language->get('error_width'); } if (!$this->request->post['height']) { $this->error['height'] = $this->language->get('error_height'); } return !$this->error; } }123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198<?phpclass ControllerExtensionModuleFeaturedPlus extends Controller { private $error = array(); public function index() { $this->load->language('extension/module/featuredplus'); $this->document->setTitle($this->language->get('heading_title')); $this->load->model('setting/module'); if (($this->request->server['REQUEST_METHOD'] == 'POST') && $this->validate()) { if (!isset($this->request->get['module_id'])) { $this->model_setting_module->addModule('featuredplus', $this->request->post); } else { $this->model_setting_module->editModule($this->request->get['module_id'], $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'] = ''; } if (isset($this->error['name'])) { $data['error_name'] = $this->error['name']; } else { $data['error_name'] = ''; } if (isset($this->error['width'])) { $data['error_width'] = $this->error['width']; } else { $data['error_width'] = ''; } if (isset($this->error['height'])) { $data['error_height'] = $this->error['height']; } else { $data['error_height'] = ''; } $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) ); if (!isset($this->request->get['module_id'])) { $data['breadcrumbs'][] = array( 'text' => $this->language->get('heading_title'), 'href' => $this->url->link('extension/module/featuredplus', 'user_token=' . $this->session->data['user_token'], true) ); } else { $data['breadcrumbs'][] = array( 'text' => $this->language->get('heading_title'), 'href' => $this->url->link('extension/module/featuredplus', 'user_token=' . $this->session->data['user_token'] . '&module_id=' . $this->request->get['module_id'], true) ); } if (!isset($this->request->get['module_id'])) { $data['action'] = $this->url->link('extension/module/featuredplus', 'user_token=' . $this->session->data['user_token'], true); } else { $data['action'] = $this->url->link('extension/module/featuredplus', 'user_token=' . $this->session->data['user_token'] . '&module_id=' . $this->request->get['module_id'], true); } $data['cancel'] = $this->url->link('marketplace/extension', 'user_token=' . $this->session->data['user_token'] . '&type=module', true); if (isset($this->request->get['module_id']) && ($this->request->server['REQUEST_METHOD'] != 'POST')) { $module_info = $this->model_setting_module->getModule($this->request->get['module_id']); } $data['user_token'] = $this->session->data['user_token']; if (isset($this->request->post['name'])) { $data['name'] = $this->request->post['name']; } elseif (!empty($module_info)) { $data['name'] = $module_info['name']; } else { $data['name'] = ''; } $this->load->model('catalog/product'); $data['products'] = array(); if (!empty($this->request->post['product'])) { $products = $this->request->post['product']; } elseif (!empty($module_info['product'])) { $products = $module_info['product']; } else { $products = array(); } $this->load->model('catalog/category'); if (isset($this->request->post['product_category'])) { $categories = $this->request->post['product_category']; } elseif (!empty($module_info['product_category'])) { $categories = $module_info['product_category']; } else { $categories = array(); } $data['product_categories'] = array(); foreach ($categories as $category_id) { $category_info = $this->model_catalog_category->getCategory($category_id); if ($category_info) { $data['product_categories'][] = array( 'category_id' => $category_info['category_id'], 'name' => ($category_info['path']) ? $category_info['path'] . ' > ' . $category_info['name'] : $category_info['name'] ); } } foreach ($products as $product_id) { $product_info = $this->model_catalog_product->getProduct($product_id); if ($product_info) { $data['products'][] = array( 'product_id' => $product_info['product_id'], 'name' => $product_info['name'] ); } } if (isset($this->request->post['limit'])) { $data['limit'] = $this->request->post['limit']; } elseif (!empty($module_info)) { $data['limit'] = $module_info['limit']; } else { $data['limit'] = 5; } if (isset($this->request->post['width'])) { $data['width'] = $this->request->post['width']; } elseif (!empty($module_info)) { $data['width'] = $module_info['width']; } else { $data['width'] = 200; } if (isset($this->request->post['height'])) { $data['height'] = $this->request->post['height']; } elseif (!empty($module_info)) { $data['height'] = $module_info['height']; } else { $data['height'] = 200; } if (isset($this->request->post['status'])) { $data['status'] = $this->request->post['status']; } elseif (!empty($module_info)) { $data['status'] = $module_info['status']; } else { $data['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/featuredplus', $data)); } protected function validate() { if (!$this->user->hasPermission('modify', 'extension/module/featuredplus')) { $this->error['warning'] = $this->language->get('error_permission'); } if ((utf8_strlen($this->request->post['name']) < 3) || (utf8_strlen($this->request->post['name']) > 64)) { $this->error['name'] = $this->language->get('error_name'); } if (!$this->request->post['width']) { $this->error['width'] = $this->language->get('error_width'); } if (!$this->request->post['height']) { $this->error['height'] = $this->language->get('error_height'); } return !$this->error; }}admin/language/en-gb/extension/module/featuredplus.phpPHP <?php // Heading $_['heading_title'] = 'Featured Plus'; // Text $_['text_extension'] = 'Extensions'; $_['text_success'] = 'Success: You have modified featured plus module!'; $_['text_edit'] = 'Edit Featured Plus Module'; // Entry $_['entry_name'] = 'Module Name'; $_['entry_product'] = 'Products'; $_['entry_limit'] = 'Limit'; $_['entry_width'] = 'Width'; $_['entry_height'] = 'Height'; $_['entry_status'] = 'Status'; $_['entry_category'] = 'Categories'; // Help $_['help_product'] = '(Autocomplete)'; $_['help_category'] = '(Autocomplete)'; // Error $_['error_permission'] = 'Warning: You do not have permission to modify featured plus module!'; $_['error_name'] = 'Module Name must be between 3 and 64 characters!'; $_['error_width'] = 'Width required!'; $_['error_height'] = 'Height required!';123456789101112131415161718192021222324252627<?php// Heading$_['heading_title'] = 'Featured Plus'; // Text$_['text_extension'] = 'Extensions';$_['text_success'] = 'Success: You have modified featured plus module!';$_['text_edit'] = 'Edit Featured Plus Module'; // Entry$_['entry_name'] = 'Module Name';$_['entry_product'] = 'Products';$_['entry_limit'] = 'Limit';$_['entry_width'] = 'Width';$_['entry_height'] = 'Height';$_['entry_status'] = 'Status';$_['entry_category'] = 'Categories'; // Help$_['help_product'] = '(Autocomplete)';$_['help_category'] = '(Autocomplete)'; // Error$_['error_permission'] = 'Warning: You do not have permission to modify featured plus module!';$_['error_name'] = 'Module Name must be between 3 and 64 characters!';$_['error_width'] = 'Width required!';$_['error_height'] = 'Height required!';admin/language/ru-ru/extension/module/featuredplus.phpPHP <?php // Heading $_['heading_title'] = 'Рекомендуемые плюс'; // Text $_['text_extension'] = 'Расширения'; $_['text_success'] = 'Настройки успешно изменены!'; $_['text_edit'] = 'Настройки модуля'; // Entry $_['entry_name'] = 'Название модуля'; $_['entry_product'] = 'Товары'; $_['entry_limit'] = 'Лимит'; $_['entry_width'] = 'Ширина'; $_['entry_height'] = 'Высота'; $_['entry_status'] = 'Статус'; $_['entry_category'] = 'Показывать в категориях'; // Help $_['help_product'] = '(Автодополнение)'; $_['help_category'] = '(Автозаполнение)'; // Error $_['error_permission'] = 'У Вас нет прав для управления данным модулем!'; $_['error_name'] = 'Название модуля должно содержать от 3 до 64 символов!'; $_['error_width'] = 'Введите ширину изображения!'; $_['error_height'] = 'Введите высоту изображения!';123456789101112131415161718192021222324252627<?php// Heading$_['heading_title'] = 'Рекомендуемые плюс'; // Text$_['text_extension'] = 'Расширения';$_['text_success'] = 'Настройки успешно изменены!';$_['text_edit'] = 'Настройки модуля'; // Entry$_['entry_name'] = 'Название модуля';$_['entry_product'] = 'Товары';$_['entry_limit'] = 'Лимит';$_['entry_width'] = 'Ширина';$_['entry_height'] = 'Высота';$_['entry_status'] = 'Статус';$_['entry_category'] = 'Показывать в категориях'; // Help$_['help_product'] = '(Автодополнение)';$_['help_category'] = '(Автозаполнение)'; // Error$_['error_permission'] = 'У Вас нет прав для управления данным модулем!';$_['error_name'] = 'Название модуля должно содержать от 3 до 64 символов!';$_['error_width'] = 'Введите ширину изображения!';$_['error_height'] = 'Введите высоту изображения!';admin/view/template/extension/module/featuredplus.twigXHTML {{ header }}{{ column_left }} <div id="content"> <div class="page-header"> <div class="container-fluid"> <div class="pull-right"> <button type="submit" form="form-module" data-toggle="tooltip" title="{{ button_save }}" class="btn btn-primary"><i class="fa fa-save"></i></button> <a href="{{ cancel }}" data-toggle="tooltip" title="{{ button_cancel }}" class="btn btn-default"><i class="fa fa-reply"></i></a></div> <h1>{{ heading_title }}</h1> <ul class="breadcrumb"> {% for breadcrumb in breadcrumbs %} <li><a href="{{ breadcrumb.href }}">{{ breadcrumb.text }}</a></li> {% endfor %} </ul> </div> </div> <div class="container-fluid"> {% if error_warning %} <div class="alert alert-danger alert-dismissible"><i class="fa fa-exclamation-circle"></i> {{ error_warning }} <button type="button" class="close" data-dismiss="alert">×</button> </div> {% endif %} <div class="panel panel-default"> <div class="panel-heading"> <h3 class="panel-title"><i class="fa fa-pencil"></i> {{ text_edit }}</h3> </div> <div class="panel-body"> <form action="{{ action }}" method="post" enctype="multipart/form-data" id="form-module" class="form-horizontal"> <div class="form-group"> <label class="col-sm-2 control-label" for="input-name">{{ entry_name }}</label> <div class="col-sm-10"> <input type="text" name="name" value="{{ name }}" placeholder="{{ entry_name }}" id="input-name" class="form-control" /> {% if error_name %} <div class="text-danger">{{ error_name }}</div> {% endif %} </div> </div> <div class="form-group"> <label class="col-sm-2 control-label" for="input-category"><span data-toggle="tooltip" title="{{ help_category }}">{{ entry_category }}</span></label> <div class="col-sm-10"> <input type="text" name="category" value="" placeholder="{{ entry_category }}" id="input-category" class="form-control" /> <div id="product-category" class="well well-sm" style="height: 150px; overflow: auto;"> {% for product_category in product_categories %} <div id="product-category{{ product_category.category_id }}"><i class="fa fa-minus-circle"></i> {{ product_category.name }} <input type="hidden" name="product_category[]" value="{{ product_category.category_id }}" /> </div> {% endfor %}</div> </div> </div> <div class="form-group"> <label class="col-sm-2 control-label" for="input-product"><span data-toggle="tooltip" title="{{ help_product }}">{{ entry_product }}</span></label> <div class="col-sm-10"> <input type="text" name="product_name" value="" placeholder="{{ entry_product }}" id="input-product" class="form-control" /> <div id="featured-product" class="well well-sm" style="height: 150px; overflow: auto;"> {% for product in products %} <div id="featured-product{{ product.product_id }}"><i class="fa fa-minus-circle"></i> {{ product.name }} <input type="hidden" name="product[]" value="{{ product.product_id }}" /> </div> {% endfor %} </div> </div> </div> <div class="form-group"> <label class="col-sm-2 control-label" for="input-limit">{{ entry_limit }}</label> <div class="col-sm-10"> <input type="text" name="limit" value="{{ limit }}" placeholder="{{ entry_limit }}" id="input-limit" class="form-control" /> </div> </div> <div class="form-group"> <label class="col-sm-2 control-label" for="input-width">{{ entry_width }}</label> <div class="col-sm-10"> <input type="text" name="width" value="{{ width }}" placeholder="{{ entry_width }}" id="input-width" class="form-control" /> {% if error_width %} <div class="text-danger">{{ error_width }}</div> {% endif %} </div> </div> <div class="form-group"> <label class="col-sm-2 control-label" for="input-height">{{ entry_height }}</label> <div class="col-sm-10"> <input type="text" name="height" value="{{ height }}" placeholder="{{ entry_height }}" id="input-height" class="form-control" /> {% if error_height %} <div class="text-danger">{{ error_height }}</div> {% endif %} </div> </div> <div class="form-group"> <label class="col-sm-2 control-label" for="input-status">{{ entry_status }}</label> <div class="col-sm-10"> <select name="status" id="input-status" class="form-control"> {% if status %} <option value="1" selected="selected">{{ text_enabled }}</option> <option value="0">{{ text_disabled }}</option> {% else %} <option value="1">{{ text_enabled }}</option> <option value="0" selected="selected">{{ text_disabled }}</option> {% endif %} </select> </div> </div> </form> </div> </div> </div> <script type="text/javascript"><!-- $('input[name=\'product_name\']').autocomplete({ source: function(request, response) { $.ajax({ url: 'index.php?route=catalog/product/autocomplete&user_token={{ user_token }}&filter_name=' + encodeURIComponent(request), dataType: 'json', success: function(json) { response($.map(json, function(item) { return { label: item['name'], value: item['product_id'] } })); } }); }, select: function(item) { $('input[name=\'product_name\']').val(''); $('#featured-product' + item['value']).remove(); $('#featured-product').append('<div id="featured-product' + item['value'] + '"><i class="fa fa-minus-circle"></i> ' + item['label'] + '<input type="hidden" name="product[]" value="' + item['value'] + '" /></div>'); } }); $('#featured-product').delegate('.fa-minus-circle', 'click', function() { $(this).parent().remove(); }); // Category $('input[name=\'category\']').autocomplete({ 'source': function(request, response) { $.ajax({ url: 'index.php?route=catalog/category/autocomplete&user_token={{ user_token }}&filter_name=' + encodeURIComponent(request), dataType: 'json', success: function(json) { response($.map(json, function(item) { return { label: item['name'], value: item['category_id'] } })); } }); }, 'select': function(item) { $('input[name=\'category\']').val(''); $('#product-category' + item['value']).remove(); $('#product-category').append('<div id="product-category' + item['value'] + '"><i class="fa fa-minus-circle"></i> ' + item['label'] + '<input type="hidden" name="product_category[]" value="' + item['value'] + '" /></div>'); } }); $('#product-category').delegate('.fa-minus-circle', 'click', function() { $(this).parent().remove(); }); //--></script></div> {{ footer }}123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161{{ header }}{{ column_left }}<div id="content"> <div class="page-header"> <div class="container-fluid"> <div class="pull-right"> <button type="submit" form="form-module" data-toggle="tooltip" title="{{ button_save }}" class="btn btn-primary"><i class="fa fa-save"></i></button> <a href="{{ cancel }}" data-toggle="tooltip" title="{{ button_cancel }}" class="btn btn-default"><i class="fa fa-reply"></i></a></div> <h1>{{ heading_title }}</h1> <ul class="breadcrumb"> {% for breadcrumb in breadcrumbs %} <li><a href="{{ breadcrumb.href }}">{{ breadcrumb.text }}</a></li> {% endfor %} </ul> </div> </div> <div class="container-fluid"> {% if error_warning %} <div class="alert alert-danger alert-dismissible"><i class="fa fa-exclamation-circle"></i> {{ error_warning }} <button type="button" class="close" data-dismiss="alert">×</button> </div> {% endif %} <div class="panel panel-default"> <div class="panel-heading"> <h3 class="panel-title"><i class="fa fa-pencil"></i> {{ text_edit }}</h3> </div> <div class="panel-body"> <form action="{{ action }}" method="post" enctype="multipart/form-data" id="form-module" class="form-horizontal"> <div class="form-group"> <label class="col-sm-2 control-label" for="input-name">{{ entry_name }}</label> <div class="col-sm-10"> <input type="text" name="name" value="{{ name }}" placeholder="{{ entry_name }}" id="input-name" class="form-control" /> {% if error_name %} <div class="text-danger">{{ error_name }}</div> {% endif %} </div> </div> <div class="form-group"> <label class="col-sm-2 control-label" for="input-category"><span data-toggle="tooltip" title="{{ help_category }}">{{ entry_category }}</span></label> <div class="col-sm-10"> <input type="text" name="category" value="" placeholder="{{ entry_category }}" id="input-category" class="form-control" /> <div id="product-category" class="well well-sm" style="height: 150px; overflow: auto;"> {% for product_category in product_categories %} <div id="product-category{{ product_category.category_id }}"><i class="fa fa-minus-circle"></i> {{ product_category.name }} <input type="hidden" name="product_category[]" value="{{ product_category.category_id }}" /> </div> {% endfor %}</div> </div> </div> <div class="form-group"> <label class="col-sm-2 control-label" for="input-product"><span data-toggle="tooltip" title="{{ help_product }}">{{ entry_product }}</span></label> <div class="col-sm-10"> <input type="text" name="product_name" value="" placeholder="{{ entry_product }}" id="input-product" class="form-control" /> <div id="featured-product" class="well well-sm" style="height: 150px; overflow: auto;"> {% for product in products %} <div id="featured-product{{ product.product_id }}"><i class="fa fa-minus-circle"></i> {{ product.name }} <input type="hidden" name="product[]" value="{{ product.product_id }}" /> </div> {% endfor %} </div> </div> </div> <div class="form-group"> <label class="col-sm-2 control-label" for="input-limit">{{ entry_limit }}</label> <div class="col-sm-10"> <input type="text" name="limit" value="{{ limit }}" placeholder="{{ entry_limit }}" id="input-limit" class="form-control" /> </div> </div> <div class="form-group"> <label class="col-sm-2 control-label" for="input-width">{{ entry_width }}</label> <div class="col-sm-10"> <input type="text" name="width" value="{{ width }}" placeholder="{{ entry_width }}" id="input-width" class="form-control" /> {% if error_width %} <div class="text-danger">{{ error_width }}</div> {% endif %} </div> </div> <div class="form-group"> <label class="col-sm-2 control-label" for="input-height">{{ entry_height }}</label> <div class="col-sm-10"> <input type="text" name="height" value="{{ height }}" placeholder="{{ entry_height }}" id="input-height" class="form-control" /> {% if error_height %} <div class="text-danger">{{ error_height }}</div> {% endif %} </div> </div> <div class="form-group"> <label class="col-sm-2 control-label" for="input-status">{{ entry_status }}</label> <div class="col-sm-10"> <select name="status" id="input-status" class="form-control"> {% if status %} <option value="1" selected="selected">{{ text_enabled }}</option> <option value="0">{{ text_disabled }}</option> {% else %} <option value="1">{{ text_enabled }}</option> <option value="0" selected="selected">{{ text_disabled }}</option> {% endif %} </select> </div> </div> </form> </div> </div> </div> <script type="text/javascript"><!--$('input[name=\'product_name\']').autocomplete({ source: function(request, response) { $.ajax({ url: 'index.php?route=catalog/product/autocomplete&user_token={{ user_token }}&filter_name=' + encodeURIComponent(request), dataType: 'json', success: function(json) { response($.map(json, function(item) { return { label: item['name'], value: item['product_id'] } })); } }); }, select: function(item) { $('input[name=\'product_name\']').val(''); $('#featured-product' + item['value']).remove(); $('#featured-product').append('<div id="featured-product' + item['value'] + '"><i class="fa fa-minus-circle"></i> ' + item['label'] + '<input type="hidden" name="product[]" value="' + item['value'] + '" /></div>'); }}); $('#featured-product').delegate('.fa-minus-circle', 'click', function() { $(this).parent().remove();}); // Category$('input[name=\'category\']').autocomplete({ 'source': function(request, response) { $.ajax({ url: 'index.php?route=catalog/category/autocomplete&user_token={{ user_token }}&filter_name=' + encodeURIComponent(request), dataType: 'json', success: function(json) { response($.map(json, function(item) { return { label: item['name'], value: item['category_id'] } })); } }); }, 'select': function(item) { $('input[name=\'category\']').val(''); $('#product-category' + item['value']).remove(); $('#product-category').append('<div id="product-category' + item['value'] + '"><i class="fa fa-minus-circle"></i> ' + item['label'] + '<input type="hidden" name="product_category[]" value="' + item['value'] + '" /></div>'); }}); $('#product-category').delegate('.fa-minus-circle', 'click', function() { $(this).parent().remove();});//--></script></div>{{ footer }}catalog/controller/extension/module/featuredplus.phpPHP <?php class ControllerExtensionModuleFeaturedPlus extends Controller { public function index($setting) { $this->load->language('extension/module/featuredplus'); $this->load->model('catalog/product'); $this->load->model('tool/image'); $data['products'] = array(); if (!$setting['limit']) { $setting['limit'] = 4; } if (isset($this->request->get['product_id'])) { $product_categories = $this->model_catalog_product->getCategories($this->request->get['product_id']); $product_in_cat = false; foreach ($product_categories as $product_category) { if (in_array($product_category['category_id'], $setting['product_category'])) { $product_in_cat = true; } } if (!$product_in_cat) { return; } } if (!empty($setting['product'])) { $products = array_slice($setting['product'], 0, (int)$setting['limit']); foreach ($products as $product_id) { $product_info = $this->model_catalog_product->getProduct($product_id); if ($product_info) { if ($product_info['image']) { $image = $this->model_tool_image->resize($product_info['image'], $setting['width'], $setting['height']); } else { $image = $this->model_tool_image->resize('placeholder.png', $setting['width'], $setting['height']); } if ($this->customer->isLogged() || !$this->config->get('config_customer_price')) { $price = $this->currency->format($this->tax->calculate($product_info['price'], $product_info['tax_class_id'], $this->config->get('config_tax')), $this->session->data['currency']); } else { $price = false; } if ((float)$product_info['special']) { $special = $this->currency->format($this->tax->calculate($product_info['special'], $product_info['tax_class_id'], $this->config->get('config_tax')), $this->session->data['currency']); } else { $special = false; } if ($this->config->get('config_tax')) { $tax = $this->currency->format((float)$product_info['special'] ? $product_info['special'] : $product_info['price'], $this->session->data['currency']); } else { $tax = false; } if ($this->config->get('config_review_status')) { $rating = $product_info['rating']; } else { $rating = false; } $data['products'][] = array( 'product_id' => $product_info['product_id'], 'thumb' => $image, 'name' => $product_info['name'], 'description' => utf8_substr(strip_tags(html_entity_decode($product_info['description'], ENT_QUOTES, 'UTF-8')), 0, $this->config->get('theme_' . $this->config->get('config_theme') . '_product_description_length')) . '..', 'price' => $price, 'special' => $special, 'tax' => $tax, 'rating' => $rating, 'href' => $this->url->link('product/product', 'product_id=' . $product_info['product_id']) ); } } } if ($data['products']) { return $this->load->view('extension/module/featuredplus', $data); } } }1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889<?phpclass ControllerExtensionModuleFeaturedPlus extends Controller { public function index($setting) { $this->load->language('extension/module/featuredplus'); $this->load->model('catalog/product'); $this->load->model('tool/image'); $data['products'] = array(); if (!$setting['limit']) { $setting['limit'] = 4; } if (isset($this->request->get['product_id'])) { $product_categories = $this->model_catalog_product->getCategories($this->request->get['product_id']); $product_in_cat = false; foreach ($product_categories as $product_category) { if (in_array($product_category['category_id'], $setting['product_category'])) { $product_in_cat = true; } } if (!$product_in_cat) { return; } } if (!empty($setting['product'])) { $products = array_slice($setting['product'], 0, (int)$setting['limit']); foreach ($products as $product_id) { $product_info = $this->model_catalog_product->getProduct($product_id); if ($product_info) { if ($product_info['image']) { $image = $this->model_tool_image->resize($product_info['image'], $setting['width'], $setting['height']); } else { $image = $this->model_tool_image->resize('placeholder.png', $setting['width'], $setting['height']); } if ($this->customer->isLogged() || !$this->config->get('config_customer_price')) { $price = $this->currency->format($this->tax->calculate($product_info['price'], $product_info['tax_class_id'], $this->config->get('config_tax')), $this->session->data['currency']); } else { $price = false; } if ((float)$product_info['special']) { $special = $this->currency->format($this->tax->calculate($product_info['special'], $product_info['tax_class_id'], $this->config->get('config_tax')), $this->session->data['currency']); } else { $special = false; } if ($this->config->get('config_tax')) { $tax = $this->currency->format((float)$product_info['special'] ? $product_info['special'] : $product_info['price'], $this->session->data['currency']); } else { $tax = false; } if ($this->config->get('config_review_status')) { $rating = $product_info['rating']; } else { $rating = false; } $data['products'][] = array( 'product_id' => $product_info['product_id'], 'thumb' => $image, 'name' => $product_info['name'], 'description' => utf8_substr(strip_tags(html_entity_decode($product_info['description'], ENT_QUOTES, 'UTF-8')), 0, $this->config->get('theme_' . $this->config->get('config_theme') . '_product_description_length')) . '..', 'price' => $price, 'special' => $special, 'tax' => $tax, 'rating' => $rating, 'href' => $this->url->link('product/product', 'product_id=' . $product_info['product_id']) ); } } } if ($data['products']) { return $this->load->view('extension/module/featuredplus', $data); } }}catalog/language/en-gb/extension/module/featuredplus.phpPHP <?php // Heading $_['heading_title'] = 'Featured'; // Text $_['text_tax'] = 'Ex Tax:';123456<?php// Heading$_['heading_title'] = 'Featured'; // Text$_['text_tax'] = 'Ex Tax:';catalog/language/ru-ru/extension/module/featuredplus.php <?php // Heading $_['heading_title'] = 'Рекомендуемые'; // Text $_['text_tax'] = 'Без НДС:';123456<?php// Heading$_['heading_title'] = 'Рекомендуемые'; // Text$_['text_tax'] = 'Без НДС:';catalog/view/theme/default/extension/module/featuredplus.twigXHTML <h3>{{ heading_title }}</h3> <div class="row"> {% for product in products %} <div class="product-layout col-lg-3 col-md-3 col-sm-6 col-xs-12"> <div class="product-thumb transition"> <div class="image"><a href="{{ product.href }}"><img src="{{ product.thumb }}" alt="{{ product.name }}" title="{{ product.name }}" class="img-responsive" /></a></div> <div class="caption"> <h4><a href="{{ product.href }}">{{ product.name }}</a></h4> <p>{{ product.description }}</p> {% if product.rating %} <div class="rating"> {% for i in 5 %} {% if product.rating < i %} <span class="fa fa-stack"><i class="fa fa-star-o fa-stack-2x"></i></span> {% else %} <span class="fa fa-stack"><i class="fa fa-star fa-stack-2x"></i><i class="fa fa-star-o fa-stack-2x"></i></span> {% endif %} {% endfor %} </div> {% endif %} {% if product.price %} <p class="price"> {% if not product.special %} {{ product.price }} {% else %} <span class="price-new">{{ product.special }}</span> <span class="price-old">{{ product.price }}</span> {% endif %} {% if product.tax %} <span class="price-tax">{{ text_tax }} {{ product.tax }}</span> {% endif %} </p> {% endif %} </div> <div class="button-group"> <button type="button" onclick="cart.add('{{ product.product_id }}');"><i class="fa fa-shopping-cart"></i> <span class="hidden-xs hidden-sm hidden-md">{{ button_cart }}</span></button> <button type="button" data-toggle="tooltip" title="{{ button_wishlist }}" onclick="wishlist.add('{{ product.product_id }}');"><i class="fa fa-heart"></i></button> <button type="button" data-toggle="tooltip" title="{{ button_compare }}" onclick="compare.add('{{ product.product_id }}');"><i class="fa fa-exchange"></i></button> </div> </div> </div> {% endfor %} </div>123456789101112131415161718192021222324252627282930313233343536373839404142<h3>{{ heading_title }}</h3><div class="row"> {% for product in products %} <div class="product-layout col-lg-3 col-md-3 col-sm-6 col-xs-12"> <div class="product-thumb transition"> <div class="image"><a href="{{ product.href }}"><img src="{{ product.thumb }}" alt="{{ product.name }}" title="{{ product.name }}" class="img-responsive" /></a></div> <div class="caption"> <h4><a href="{{ product.href }}">{{ product.name }}</a></h4> <p>{{ product.description }}</p> {% if product.rating %} <div class="rating"> {% for i in 5 %} {% if product.rating < i %} <span class="fa fa-stack"><i class="fa fa-star-o fa-stack-2x"></i></span> {% else %} <span class="fa fa-stack"><i class="fa fa-star fa-stack-2x"></i><i class="fa fa-star-o fa-stack-2x"></i></span> {% endif %} {% endfor %} </div> {% endif %} {% if product.price %} <p class="price"> {% if not product.special %} {{ product.price }} {% else %} <span class="price-new">{{ product.special }}</span> <span class="price-old">{{ product.price }}</span> {% endif %} {% if product.tax %} <span class="price-tax">{{ text_tax }} {{ product.tax }}</span> {% endif %} </p> {% endif %} </div> <div class="button-group"> <button type="button" onclick="cart.add('{{ product.product_id }}');"><i class="fa fa-shopping-cart"></i> <span class="hidden-xs hidden-sm hidden-md">{{ button_cart }}</span></button> <button type="button" data-toggle="tooltip" title="{{ button_wishlist }}" onclick="wishlist.add('{{ product.product_id }}');"><i class="fa fa-heart"></i></button> <button type="button" data-toggle="tooltip" title="{{ button_compare }}" onclick="compare.add('{{ product.product_id }}');"><i class="fa fa-exchange"></i></button> </div> </div> </div> {% endfor %}</div>Теперь помещаем все это в директорию "upload", а ее в свою очередь в архив "featuredplus.oxmod.zip".Модуль готов.Установите его через установку расширений и пользуйтесь.Как упростить создание модуляСогласитесь, что создать 31 директорию, 8 файлов и хотя бы скопировать код каждого файла - дело достаточно трудоемкое и отнимает много времени.Но есть выход. Просто устанавливаем модуль "Создание модулей"После установки открываем в админке модуль "Создание модулей", задаем некоторые настройки и нажимаем сохранить.При этом у нас есть 2 варианта:Сохранить существующий модуль Очень полезно, если вы разрабатываете модуль и по окончанию разработки его нужно сохранить в отдельные директории. Делается это одной кнопкой "Сохранить"Создать новый Задаем настройки, наименование модуля, заголовок и получаем готовый для установки .ocmod.zip файл Устанавливаем его, правим так как нам надо и опять сохраняем с помощью "Создание модулей"Если у Вас остались вопросы, предложения, пожелания - пишите в комментариях.Если вам требуется написать модуль под заказ - опишите какой функционал вы ожидаете от модуля и мы с вами свяжимся Ваше имя (обязательно) Ваш e-mail (обязательно) Модуль Описание модуля
Виктор 04.06.2019 в 09:20Форма связи для заказа модуля не работает. При попытке её заполнить всё помечает как спам, даже имя. Ответить ↓