Модуль «Сообщить об ошибке» для Prestashop
Когда-то, для одной организации разрабатывал интернет-магазин на базе популярного бесплатного французского движка Prestashop 1.6.0.12. На данный момент уже доступна более новая версия. Так вот, необходимо было разработать модуль, умеющий отправлять сообщение об ошибке в тексте. Пользователь выделяет слово или фразу, а затем нажимает Ctrl+Enter, сообщение об ошибке отправляется администратору сайта.
Данный модуль простой и не является чем-то особенным. Тестировался на версии 1.6.012. Устанавливается модуль просто, через интерфейс сайта. Итак, приступим.
Для начала создадим папку с именем blockcheckspelling, которая будет содержать все файлы модуля и будет располагаться в папке mosules корня движка Prestashop.
Внутри каталога blockcheckspelling создадим еще 3 папки: css, mails и translations. В css будет размещен файл стилей, в mails — шаблоны писем на нужном языке, в translations — соответственно перевод сообщений на нужный нам язык.
Для начала работы модуля создадим среди данных трех каталогов файл с именем blockcheckspelling.php и следующего содержания:
<?php /* * @author Denis Sitko <info@sitkodenis.ru> * @copyright 2015 Denis Sitko * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ if (!defined('_CAN_LOAD_FILES_')) exit; class BlockCheckSpelling extends Module { public function __construct() { $this->name = 'blockcheckspelling'; $this->tab = 'front_office_features'; $this->version = '1.0'; $this->author = 'Denis Sitko'; $this->controllers = ['form', 'senderror']; $this->need_instance = 0; $this->bootstrap = true; parent::__construct(); $this->displayName = $this->l('Error in the text'); $this->description = $this->l('Sending a visitor notices an error on the site.'); $this->ps_versions_compliancy = ['min' => '1.6', 'max' => _PS_VERSION_]; } public function install($delete_params = true) { if (!parent::install() || !$this->registerHook('header') || !$this->registerHook('displayCheckSpelling')) return false; if ($delete_params) { Configuration::updateValue('BLOCKCHECKSPELLING_EMAIL', Configuration::get('PS_SHOP_EMAIL')); } return true; } public function uninstall($delete_params = true) { if ($delete_params) { Configuration::deleteByName('BLOCKCHECKSPELLING_EMAIL'); } return parent::uninstall(); } public function hookHeader($params) { $this->context->controller->addCss($this->_path . '/css/blockcheckspelling.css'); $this->context->controller->addJqueryPlugin('fancybox'); } public function hookDisplayCheckSpelling($params) { return $this->display(__FILE__, 'block.tpl'); } public function hookAjaxForm() { return $this->display(__FILE__, 'form.tpl'); } public function getContent() { $errors = []; $output = null; if (Tools::isSubmit('submit'.$this->name)) { if (!Configuration::updateValue('BLOCKCHECKSPELLING_EMAIL', (string)Tools::getValue('BLOCKCHECKSPELLING_EMAIL'))) $errors[] = $this->l('Cannot update settings'); if (count($errors) > 0) $output .= $this->displayError(implode('<br />', $errors)); else $output .= $this->displayConfirmation($this->l('Settings updated successfully')); } return $output.$this->displayForm(); } public function displayForm() { $default_lang = (int)Configuration::get('PS_LANG_DEFAULT'); $fields_form[0]['form'] = [ 'legend' => [ 'title' => $this->l('Settings'), 'icon' => 'icon-cog' ], 'input' => [ [ 'type' => "text", 'name' => 'BLOCKCHECKSPELLING_EMAIL', 'label' => $this->l('Email'), ], ], 'submit' => [ 'title' => $this->l('Save'), 'class' => 'btn btn-default pull-right' ] ]; $helper = new HelperForm(); $helper->module = $this; $helper->name_controller = $this->name; $helper->token = Tools::getAdminTokenLite('AdminModules'); $helper->currentIndex = AdminController::$currentIndex.'&configure='.$this->name; $helper->default_form_language = $default_lang; $helper->allow_employee_form_lang = $default_lang; $helper->title = $this->displayName; $helper->show_toolbar = true; $helper->toolbar_scroll = true; $helper->submit_action = 'submit'.$this->name; $helper->toolbar_btn = [ 'save' => [ 'desc' => $this->l('Save'), 'href' => AdminController::$currentIndex.'&configure='.$this->name.'&save'.$this->name. '&token='.Tools::getAdminTokenLite('AdminModules'), ] ]; $helper->fields_value['BLOCKCHECKSPELLING_EMAIL'] = (string)Configuration::get('BLOCKCHECKSPELLING_EMAIL'); return $helper->generateForm($fields_form); } public function sendError($errorText) { $template_vars = array( '{errorText}' => Tools::safeOutput($errorText), '{shop_name}' => Configuration::get('PS_SHOP_NAME'), '{date}' => date('d.m.Y H:i:s') ); return Mail::Send($this->context->language->id, 'blockcheckspelling', 'Орфографическая ошибка на сайте', $template_vars, Configuration::get('BLOCKCHECKSPELLING_EMAIL'), null, Configuration::get('PS_SHOP_EMAIL'), Configuration::get('PS_SHOP_NAME'), null, null, dirname(__FILE__).'/mails/', false, $this->context->shop->id ); } }
Данный файл является основным загружаемым файлом модуля. Здесь в конструкторе мы задаем основные настройки модуля. В свойстве
$this->controllers = ['form', 'senderror'];
перечисляться дополнительные имена контроллеров. Давайте их создадим.
Файл form.php для вызова формы:
<?php /* * @author Denis Sitko <info@sitkodenis.ru> * @copyright 2015 Denis Sitko * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ include_once(dirname(__FILE__).'/../../config/config.inc.php'); include_once(dirname(__FILE__).'/blockcheckspelling.php'); $blockCheckSpelling = new BlockCheckSpelling(); echo $blockCheckSpelling->hookAjaxForm();
Файл senderror.php для отправки сообщения об ошибке:
<?php /* * @author Denis Sitko <info@sitkodenis.ru> * @copyright 2015 Denis Sitko * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ include_once(dirname(__FILE__).'/../../config/config.inc.php'); include_once(dirname(__FILE__).'/blockcheckspelling.php'); $blockCheckSpelling = new BlockCheckSpelling(); $errorText = trim($_POST['errorText']); if (!empty($errorText) && Validate::isMessage($errorText)){ $blockCheckSpelling->sendError($errorText); Tools::redirect('index.php'); }
Здесь же в корне модуля разместим файл index.php, его можно скопировать из любого другого существующего модуля. Еще необходимо приготовить и разместить в этом же каталоге 2 файла: logo.png и logo.gif (размером 16x16px).
Для корректной установки модуля создадим 2 конфигурационных файла.
Файл config.xml
<?xml version="1.0" encoding="UTF-8" ?> <module> <name>blockcallme</name> <displayName><![CDATA[Error in the text]]></displayName> <version><![CDATA[1.0]]></version> <description><![CDATA[Sending a visitor notices an error on the site.]]></description> <author><![CDATA[Denis Sitko]]></author> <tab><![CDATA[front_office_features]]></tab> <is_configurable>1</is_configurable> <need_instance>0</need_instance> <limited_countries></limited_countries> </module>
Файл config_ru.xml
<?xml version="1.0" encoding="UTF-8" ?> <module> <name>blockcheckspelling</name> <displayName><![CDATA[Ошибка в тексте]]></displayName> <version><![CDATA[1.0]]></version> <description><![CDATA[Отправка посетителем уведомления об ошибке на сайте.]]></description> <author><![CDATA[Denis Sitko]]></author> <tab><![CDATA[front_office_features]]></tab> <is_configurable>1</is_configurable> <need_instance>0</need_instance> <limited_countries></limited_countries> </module>
В функции install() при установке мы создаем новый параметр в конфигурации Perstashop.
Configuration::updateValue('BLOCKCHECKSPELLING_EMAIL', Configuration::get('PS_SHOP_EMAIL'));
Аналогично, в функции uninstall() мы его удаляем из системы.
В функции hookHeader() мы подключаем необходимые нам для работы файлы. Fancybox уже имеется в системе изначально, так что его просто подключаем строкой
$this->context->controller->addJqueryPlugin('fancybox');
А вот файл со стилями blockcheckspelling.css создадим и разместим в папке css.
#blockcheckspelling { float: left; margin: 0 20px 0 20px; padding: 3px; width: 350px; font-size: 12px; color: #ccc; } #blockcheckspelling span.keys { color: #fff; background-color: #3a8a41; padding: 3px; -webkit-border-radius: 10px 2px 10px 2px; -moz-border-radius: 10px 2px 10px 2px; border-radius: 10px 2px 10px 2px; } .block-call-window-cs { display: none; } hr { border-bottom: 1px solid #99CC66; box-shadow: 0 0 10px rgba(0,153,0,0.5); } h2.blockcheckspelling { font-size: 16px; font-weight: bold; margin: 15px 0 0 15px; color: #99CC66; text-shadow: 2px 2px 3px rgba(0,0,0,0.1); } .blockcheckspelling label { margin: 0 0 0 20px; } form.blockcheckspelling { margin: 0 auto; margin-top: 20px; } .blockcheckspelling input { font-family: "Helvetica Neue", Helvetica, sans-serif; font-size: 12px; outline: none; } .blockcheckspelling input[type=text] { color: #777; padding-left: 10px; margin: 10px; margin-top: 12px; margin-left: 18px; width: 93%; height: 35px; border: 1px solid #097b33; border-radius: 2px; box-shadow: inset 0 1.5px 3px rgba(190, 190, 190, .4), 0 0 0 5px #f5f7f8; -webkit-transition: all .4s ease; -moz-transition: all .4s ease; transition: all .4s ease; } .blockcheckspelling input[type=text]:hover { border: 1px solid #99CC66; box-shadow: inset 0 1.5px 3px rgba(190, 190, 190, .7), 0 0 0 5px #f5f7f8; } .blockcheckspelling input[type=text] { border: 1px solid #99CC66; box-shadow: inset 0 1.5px 3px rgba(190, 190, 190, .4), 0 0 0 5px #e6f2f9; } .blockcheckspelling input[type=submit] { float: right; margin-right: 20px; margin-top: 20px; width: 180px; height: 30px; font-size: 12px; font-weight: bold; color: #fff; background-color: #097b33; border: 1px solid #097b33; cursor: pointer; } .blockcheckspelling input[type=submit]:hover, .blockcheckspelling input[type=submit]:active { background-color: #669966; } .blockcheckspelling input[type=submit]:disabled { background-color: #E8E8E8; border: 1px solid #C8C8C8; color: #C0C0C0; }
Функция hookDisplayCheckSpelling() описывает хук, который подключает html-код и скрипт для вызова окна с сообщением об ошибке. Для данной функции создадим здесь же файл с именем block.tpl
<div id="blockcheckspelling"> <span class="descr">{l s='Found a mistake? Select it and click' mod='blockcheckspelling'} <span class="keys">Ctrl + Enter</span></span> </div> <div class="block-call-window-cs"></div> <script> $(function(){ var selectText = function(){ var text = ""; if (window.getSelection) { text = window.getSelection(); }else if (document.getSelection) { text = document.getSelection(); }else if (document.selection) { text = document.selection.createRange().text; } return text.toString(); }; $(".block-call-window-cs").on("click", function (e) { e.preventDefault(); $.ajax({ type: "POST", cache: false, url: "/modules/blockcheckspelling/form.php", data: { text : selectText }, success: function (data) { $.fancybox(data, { fitToView: false, width: 520, height: 210, autoSize: false, openEffect: 'none', closeEffect: 'none' }); } }); }); $(document).on("keydown", function(e) { if ((e.keyCode == 10 || e.keyCode == 13) && e.ctrlKey) { $(".block-call-window-cs").trigger("click"); } }); }); </script>
Кстати, не забудьте подключить ваш хук в удобном для вас месте
{hook h="displayCheckSpelling"}
Теперь создадим в корне файл form.tpl, отвечающий за вид отображаемой формы
<h2 class="blockcheckspelling">{l s='Share a mistake in the text' mod='blockcheckspelling'}</h2> <hr /> <form class="blockcheckspelling" action="{$module_dir}senderror.php" method="post"> <label for="errorText">{l s='Spelling error in the phrase:' mod='blockcheckspelling'}</label> <input type="text" name="errorText" id="errorText" value="{$smarty.post.text}" /> <input type="submit" value="{l s='Send an error' mod='blockcheckspelling'}" /> </form> <script> $(function(){ var errorText = $("#errorText").val(); var $submit = $(".blockcheckspelling").find("input[type=submit]"); if (errorText.length > 0){ $submit.attr("disabled", false); } else { $submit.attr("disabled", true); } $("form").on("change", "#errorText", function(){ if ($(this).val().length > 0){ $submit.attr("disabled", false); } else { $submit.attr("disabled", true); } }); }); </script>
В функции getContent() используется для сохранения настроек в админке данного модуля. Сам вывод полей формы для настроек находится в функции displayForm().
Функция sendError() используется контроллером senderror, про содержимое которого я писал уже выше. В функции происходит отправка данных на указанный адрес электронной почты.
Последнее, что осталось, разместить 2 файла с шаблонами писем в директории mails/ru/.
Файл blockcheckspelling.html
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/1999/REC-html401-19991224/strict.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" /> <title>Сообщение от магазина {shop_name}</title> <style> @media only screen and (max-width: 300px){ body { width:218px !important; margin:auto !important; } .table {width:195px !important;margin:auto !important;} .logo, .titleblock, .box, .footer, .space_footer{width:auto !important;display: block !important;} span.title{font-size:20px !important;line-height: 23px !important} td.box p{font-size: 12px !important;font-weight: bold !important;} .table-recap table, .table-recap thead, .table-recap tbody, .table-recap th, .table-recap td, .table-recap tr { display: block !important; } .table-recap tr td, .conf_body td{text-align:center !important;} } @media only screen and (min-width: 301px) and (max-width: 500px) { body {width:308px!important;margin:auto!important;} .table {width:285px!important;margin:auto!important;} .logo, .titleblock, .linkbelow, .box, .footer, .space_footer{width:auto!important;display: block!important;} .table-recap table, .table-recap thead, .table-recap tbody, .table-recap th, .table-recap td, .table-recap tr { display: block !important; } .table-recap tr td, .conf_body td{text-align:center !important;} } @media only screen and (min-width: 501px) and (max-width: 768px) { body {width:478px!important;margin:auto!important;} .table {width:450px!important;margin:auto!important;} .logo, .titleblock, .box, .footer, .space_footer{width:auto!important;display: block!important;} } @media only screen and (max-device-width: 480px) { body {width:308px!important;margin:auto!important;} .table {width:285px;margin:auto!important;} .logo, .titleblock, .box, .footer, .space_footer{width:auto!important;display: block!important;} .table-recap tr td, .conf_body td{text-align:center!important;} } </style> </head> <body style="-webkit-text-size-adjust:none;background-color:#fff;width:650px;font-family:Open-sans, sans-serif;color:#555454;font-size:13px;line-height:18px;margin:auto"> <table class="table table-mail" style="width:100%;margin-top:10px;-moz-box-shadow:0 0 5px #afafaf;-webkit-box-shadow:0 0 5px #afafaf;-o-box-shadow:0 0 5px #afafaf;box-shadow:0 0 5px #afafaf;filter:progid:DXImageTransform.Microsoft.Shadow(color=#afafaf,Direction=134,Strength=5)"> <tr> <td class="space" style="width:20px;padding:7px 0"> </td> <td align="center" style="padding:7px 0"> <table class="table" bgcolor="#ffffff" style="width:100%"> <tr> <td align="center" class="logo" style="border-bottom:4px solid #333333;padding:7px 0"> <a title="{shop_name}" href="{shop_url}" style="color:#337ff1"> <img src="{shop_logo}" alt="{shop_name}" /> </a> </td> </tr> <tr> <td align="center" class="titleblock" style="padding:7px 0"> <font size="2" face="Open-sans, sans-serif" color="#555454"> <span class="title" style="font-weight:500;font-size:28px;text-transform:uppercase;line-height:33px">Здравствуйте,</span> </font> </td> </tr> <tr> <td class="space_footer" style="padding:0!important"> </td> </tr> <tr> <td class="box" style="border:1px solid #D6D4D4;background-color:#f8f8f8;padding:7px 0"> <table class="table" style="width:100%"> <tr> <td width="10" style="padding:7px 0"> </td> <td style="padding:7px 0"> <font size="2" face="Open-sans, sans-serif" color="#555454"> <p data-html-only="1" style="border-bottom:1px solid #D6D4D4;margin:3px 0 7px;text-transform:uppercase;font-weight:500;font-size:18px;padding-bottom:10px"> На сайте была обнаружена ошибка </p> <span style="color:#777">Фраза: <strong>{fio}</strong></span> <span style="color: #777">Дата: <strong>{date}</strong></span> </font> </td> <td width="10" style="padding:7px 0"> </td> </tr> </table> </td> </tr> <tr> <td class="space_footer" style="padding:0!important"> </td> </tr> <tr> <td class="footer" style="border-top:4px solid #333333;padding:7px 0"> <span><a href="{shop_url}" style="color:#337ff1">{shop_name}</a></span> </td> </tr> </table> </td> <td class="space" style="width:20px;padding:7px 0"> </td> </tr> </table> </body> </html>
Файл blockcheckspelling.txt
{shop_name} Здравствуйте, На сайте была обнаружена ошибка в следующей фразе {errorText} Дата: {date}
Ну и конечно, не забываем положить файл с переводом в папку translations (ru.php)
<?php global $_MODULE; $_MODULE = array(); $_MODULE['<{blockcheckspelling}prestashop>block_9acb1671e5f7efb984602b324eb8bf4e'] = 'Нашли ошибку? Выделите ее и нажмите'; $_MODULE['<{blockcheckspelling}prestashop>blockcheckspelling_519719c01844d3991a43458039ed5a6b'] = 'Ошибка в тексте'; $_MODULE['<{blockcheckspelling}prestashop>blockcheckspelling_ea400a8ae277d9aa56116509bf1807f1'] = 'Отправка посетителем уведомления об ошибке на сайте.'; $_MODULE['<{blockcheckspelling}prestashop>blockcheckspelling_c1ee76f076a5b97e3b4b0c0e5703246e'] = 'Невозможно обновить настройки'; $_MODULE['<{blockcheckspelling}prestashop>blockcheckspelling_462390017ab0938911d2d4e964c0cab7'] = 'Настройки успешно обновлены'; $_MODULE['<{blockcheckspelling}prestashop>blockcheckspelling_f4f70727dc34561dfde1a3c529b6205c'] = 'Настройки'; $_MODULE['<{blockcheckspelling}prestashop>blockcheckspelling_ce8ae9da5b7cd6c3df2929543a9af92d'] = 'Электронная почта'; $_MODULE['<{blockcheckspelling}prestashop>blockcheckspelling_c9cc8cce247e49bae79f15173ce97354'] = 'Сохранить'; $_MODULE['<{blockcheckspelling}prestashop>form_15c1decf6b1b7add4b3cbc6c44de4f12'] = 'Поделиться ошибкой в тексте'; $_MODULE['<{blockcheckspelling}prestashop>form_11f3a9d7193127816c0db3690b5113cc'] = 'Орфографическая ошибка в фразе:'; $_MODULE['<{blockcheckspelling}prestashop>form_01881661b0715d201d4bc18826a48d33'] = 'Отправить ошибку';
Вот и все. Было не так и сложно. Конечно, данный код далеко не идеален, можно было бы некоторые моменты сделать по другому. Но я не квалифицируюсь в области разработки решений для Prestashop, просто в один момент была задача и ее надо было решить как можно скорее.
Да, для ленивых прилагаю архив с исходниками модуля.
Спасибо и до новых встреч.