Небольшой, но подробный туториал для начинающих разработчиков.
Возникла потребность в плавающей, выдвигающейся сбоку форме обратной связи (обратный звонок). К сожалению, готового решения я так и не нашел, кроме громоздкого, через модуль vqmod. Его суть заключается в перезаписи файлов OpenCart на лету, этакий своеобразный прокси-компонент, или декоратор, если угодно. Идея, конечно, интересная, но не об этом сейчас.
Мы займемся решением задачи с нуля, рассмотрим в деталях, как это делается. Надеюсь, материал данной статьи поможет вам лучше ориентироваться в OpenCart.
Цель
В развернутом виде, наша форма будет выглядеть так
Отправлять данные будем с помощью AJAX, то есть без перезагрузки страницы. Потребуется написать под это дело контроллер, исправить темплейт header текущей (дефолтной в нашем случае) темы, добавить стили и JavaScript.
Полный список редактируемых файлов:
- /catalog/view/theme/default/template/common/header.tpl
- /catalog/view/theme/default/stylesheet/stylesheet.css
- /catalog/view/javascript/common.js
- /catalog/controller/ajax/feedback.php – создаем папку и файл
Сперва, создадим код нашей формы:
<!-- contact form start --> <div class="floating-form" id="contact_form"> <div class="contact-opener"> <span class="glyphicon glyphicon-phone-alt"></span> обратный звонок</div> <div class="floating-form-heading">Заполните форму</div> <div id="contact_results"></div> <div id="contact_body"> <label><span>Имя <span class="required">*</span></span> <input type="text" name="name" id="name" required="required" class="input-field"> </label> <label><span>Телефон <span class="required">*</span></span> <input type="text" name="phone2" maxlength="20" required="required" class="input-field"> </label> <label><span>Текст </span> <textarea name="message" id="message" class="textarea-field" required="required"></textarea> </label> <label> <span> </span><input type="submit" id="submit_btn" value="отправить"> </label> </div> </div> <!-- contact form end -->
Затем, вставим в header.tpl, сразу после открывающего тэга body. Полный путь здесь и далее смотри выше, в списке файлов.
Теперь добавим стили в самый конец stylesheet.css
Большая CSS простыня (кликни)
/*Floating form css begin*/ .floating-form { max-width: 310px; padding: 30px 30px 10px 30px; font: 13px Arial, Helvetica, sans-serif; /*background: #F9F9F9;*/ background: #FFF9B4; border: 1px solid #ddd; right: 0px; position: fixed; box-shadow: -2px -0px 8px rgba(43, 33, 33, 0.06); -moz-box-shadow: -2px -0px 8px rgba(43, 33, 33, 0.06); -webkit-box-shadow: -2px -0px 8px rgba(43, 33, 33, 0.06); z-index: 1000; -webkit-box-shadow: -5px 5px 17px -4px rgba(0,0,0,0.75); -moz-box-shadow: -5px 5px 17px -4px rgba(0,0,0,0.75); box-shadow: -5px 5px 17px -4px rgba(0,0,0,0.75); border-radius: 10px; opacity: 0.98; right: -290px; } .contact-opener { position: absolute; left: -110px; transform: rotate(-90deg); top: 100px; background-color: #216288; padding: 9px; color: #fff; text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.43); cursor: pointer; border-radius: 5px 5px 0px 0px; -webkit-border-radius: 5px 5px 0px 0px; -moz-border-radius: 5px 5px 0px 0px; box-shadow: -2px -0px 8px rgba(43, 33, 33, 0.06); -moz-box-shadow: -2px -0px 8px rgba(43, 33, 33, 0.06); -webkit-box-shadow: -2px -0px 8px rgba(43, 33, 33, 0.06); font-size: 16px !important; } .floating-form-heading{ font-weight: bold; font-style: italic; border-bottom: 2px solid #ddd; margin-bottom: 10px; font-size: 15px; padding-bottom: 3px; } .floating-form label{ display: block; margin: 0px 0px 15px 0px; } .floating-form label > span{ width: 70px; font-weight: bold; float: left; padding-top: 8px; padding-right: 5px; } .floating-form span.required{ color:red; } .floating-form .tel-number-field{ width: 40px; text-align: center; } .floating-form .long{ width: 120px; } .floating-form input.input-field{ width: 68%; } .floating-form input.input-field, .floating-form .tel-number-field, .floating-form .textarea-field, .floating-form .select-field{ -webkit-transition: all 0.30s ease-in-out; -moz-transition: all 0.30s ease-in-out; -ms-transition: all 0.30s ease-in-out; -o-transition: all 0.30s ease-in-out; box-sizing: border-box; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; border: 1px solid #C2C2C2; box-shadow: 1px 1px 4px #EBEBEB; -moz-box-shadow: 1px 1px 4px #EBEBEB; -webkit-box-shadow: 1px 1px 4px #EBEBEB; border-radius: 3px; -webkit-border-radius: 3px; -moz-border-radius: 3px; padding: 7px; outline: none; } .floating-form .input-field:focus, .floating-form .tel-number-field:focus, .floating-form .textarea-field:focus, .floating-form .select-field:focus{ border: 1px solid #0C0; } .floating-form .textarea-field{ height:100px; width: 68%; } .floating-form input[type="button"], .floating-form input[type="submit"], .contact-opener { -moz-box-shadow: inset 0px 1px 0px 0px #3985B1; -webkit-box-shadow: inset 0px 1px 0px 0px #3985B1; box-shadow: inset 0px 1px 0px 0px #3985B1; background-color: #216288; border: 1px solid #17445E; display: inline-block; cursor: pointer; color: #FFFFFF; padding: 8px 18px; text-decoration: none; font: 12px Arial, Helvetica, sans-serif; } .floating-form input[type="submit"] { border-radius: 5px; width: 157px; } .floating-form input[type="button"]:hover, .floating-form input[type="submit"]:hover, .contact-opener { background: linear-gradient(to bottom, #2D77A2 5%, #337DA8 100%); background-color: #28739E; } .floating-form .success{ background: #D8FFC0; padding: 5px 10px 5px 10px; margin: 0px 0px 5px 0px; border: none; font-weight: bold; color: #2E6800; border-left: 3px solid #2E6800; } .floating-form .error { background: #FFE8E8; padding: 5px 10px 5px 10px; margin: 0px 0px 5px 0px; border: none; font-weight: bold; color: #FF0000; border-left: 3px solid #FF0000; } /*Floating form css end*/
Найдем в common.js строку $(document).ready(function() {, добавим после нее код валидации и отправки формы через AJAX:
JavaScript
//// Floating form handler begin var _scroll = true, _timer = false, _floatbox = $("#contact_form"); var _floatbox_opener = $(".contact-opener") ; //Прячем или показываем форму _floatbox_opener.click(function(){ if (_floatbox.hasClass('visiable')){ _floatbox.animate({"right":"-290px"}, {duration: 300}).removeClass('visiable'); } else { _floatbox.animate({"right":"0px"}, {duration: 300}).addClass('visiable'); } }); //Эффект полета. Если нужен, раскомментируйте блок //$(window).scroll(function(){ // if(_scroll){ // _floatbox.animate({"top": "30px"},{duration: 300}); // _scroll = false; // } // // if(_timer !== false){ clearTimeout(_timer); } // _timer = setTimeout(function(){ // _scroll = true; // _floatbox.animate({"top": "10px"},{easing: "linear"}, {duration: 500});}, 400); //}); //Отправка при нажатии кнопки $("#submit_btn").click(function() { var proceed = true; //Простая валидация на стороне клиента //Пробегаем по каждому полю и просто меняем цвет рамки на красный, //в случае ошибки заполнения $("#contact_form input[required=required]").each(function(){ $(this).css('border-color',''); //если поле пустое if(!$.trim($(this).val())){ $(this).css('border-color','red'); //меняем цвета рамки на красный proceed = false; //устанавливаем «стоп» флаг. } }); if (proceed) { //валидация прошла без ошибок, идем дальше. //извлекаем содержимое полей нашей формы, создаем объект post_data = { 'user_name' : $('input[name=name]').val(), 'phone_number' : $('input[name=phone2]').val(), 'msg': $('textarea[name=message]').val() }; //Передаем форму через AJAX методом POST $.post('/?route=ajax/feedback/ajaxSend', post_data, function(response){ if(response.type == 'error'){ //обрабатываем ответ сервера output = '<div class="error">'+response.text+'</div>'; } else { output = '<div class="success">'+response.text+'</div>'; //очищаем поля фрмы $("#contact_form input[required=required], #contact_form textarea").val(''); } $("#contact_form #contact_results").hide().html(output).slideDown(); }, 'json'); } }); //убираем красную рамку у полей, которые начал исправлять пользователь $("#contact_form input[required=required]").keyup(function() { $(this).css('border-color',''); $("#result").slideUp(); }); ////floating form handler end
Теперь создадим папку ajax и контроллер feedback, следующего содержания:
Код контроллера
<?php // catalog/controller/ajax/feedback.php class ControllerAjaxFeedback extends Controller { public function ajaxSend() { //проверяем была ли отправлена форма. Если нет, завершаем работу контроллера if ($this->request->server['REQUEST_METHOD'] !== 'POST'){ return false; } //извлекаем имя домена из настроек CMS. $parse = parse_url($this->config->get('config_url')); $domain = $parse['host']; //поскольку ответ отправляем в формате JSON, необходимо установить заголовок $this->response->addHeader('Content-Type: application/json'); //здесь валидация того, что форма отправлена именно через AJAX. //просто как дополнительная защита от спама if (!isset($this->request->server['HTTP_X_REQUESTED_WITH']) && strtolower($this->request->server['HTTP_X_REQUESTED_WITH']) != 'xmlhttprequest' ) { $output = json_encode([ 'type'=>'error', 'text' => 'Sorry Request must be Ajax POST', ]); $this->response->setOutput($output); return false; } $user_name = $this->request->post['user_name']; $phone_number = $this->request->post['phone_number']; $message = $this->request->post['msg']; //проверяем длину имени пользователя if (strlen($user_name) < 4) { $output = json_encode([ 'type'=>'error', 'text' => 'Слишком короткое имя', ]); $this->response->setOutput($output); return false; } if (!filter_var($phone_number, FILTER_SANITIZE_NUMBER_FLOAT)) { $output = json_encode([ 'type'=>'error', 'text' => 'Некорректный телефон', ]); $this->response->setOutput($output); return false; } // устанавливаем дополнительный параметр для корректного return-path $mail = new Mail(['parameter' => '-finfo@' . $domain]); //извлекаем различные данные из настроек магазина и заполняем в экземпляр mail. $mail->protocol = $this->config->get('config_mail_protocol'); //тут смотрим есть ли в конфигах дополнительные параметры. //вероятно флаг –f или иной тюнинг был указан в админке, тогда перезаписываем. if (!empty($this->config->get('config_mail_parameter'))) { $mail->parameter = $this->config->get('config_mail_parameter'); } $mail->smtp_hostname = $this->config->get('config_mail_smtp_hostname'); $mail->smtp_username = $this->config->get('config_mail_smtp_username'); $mail->smtp_password = $this->config->get('config_mail_smtp_password'); $mail->smtp_port = $this->config->get('config_mail_smtp_port'); $mail->smtp_timeout = $this->config->get('config_mail_smtp_timeout'); $mail->setTo($this->config->get('config_email')); //если отправляем через SMTP, то выставляем поле From в корректное значение. //иначе отправляем от имени info@вашдомен if ($mail->protocol !== 'mail') { $mail->setFrom($mail->smtp_username); } else { $mail->setFrom('info@' . $domain); } $mail->setSender($domain); $mail->setSubject('Сообщение с сайта ' . $domain); $mail->setText('Имя отправителя: ' . $user_name . "\r\nТелефон: " . $phone_number . "\r\nСообщение: " . $message); $mail->send(); $this->response->setOutput(json_encode([ 'type'=>'message', 'text' => 'Ваше сообщение отправлено!', ])); } }
Адрес, на который отправляются письма, берем из настроек OpenCart и другие настройки, связанные с почтой. Контроллер работает как в режиме «Mail» так и «SMTP».

Данное решение тщательно протестировано на версии OpenCart 2.3.0.2 (русская сборка).
Чтобы ваши письма не попадали в СПАМ, воспользуйтесь рекомендациями из ранее опубликованной статьи
root4root aka admin
[…] ← Предыдущая […]
Сделал все как написано в статье, но при нажатии на кнопку отправить ничего не происходит, поля все заполнены, OpenCart 2.3.
Как ничего не происходит, вообще глухо? Или есть надпись, что сообщение отправлено, но фактически на почту не приходит?
Если ошибка (скорее опечатка тогда уж) всё-таки закралась во время оформления статьи, то я ее обязательно исправлю. Но это очень странно, код лично проверял, более того, — он работает на нескольких сайтах. Предоставьте, пожалуйста, больше информации по диагностике. Что в консоли браузера, например. Запрос проходит? Что отвечает сервер?
Наткнулся на данную статью в 2020, ошибка такая, что нужно указывать e-mail отправителя и получателя, в данном случае отправляется письмо самому себе, добавляется после $mail->smtp_timeout = $this->config->get(‘config_mail_smtp_timeout’);
$mail->setTo($this->config->get(‘config_email’));
$mail->setFrom($this->config->get(‘config_email’));
Привет, спасибо за статью, перепиливаю под себя, но сейчас после всех заливки всех ф-в на сервер мне выдаёт что post_data не определён http://prntscr.com/iumurq
Добавьте var перед post_data
добавил переменную массивом post_data = []; и вроде 200, но на почту ничего не пришло, хотя ставил свою дополнительной http://prntscr.com/iumwdz
Нет, там объект должен быть, а не массив. На дополнительную почту не должно приходить сообщение, только на основную. Чтобы добавить возможность, нужно примерно так в PHP контроллере:
Почитайте класс Mail, который находится system/library/mail.php, вам многое станет понятно. Они его периодически изменяют, кстати.
Если письма не приходят, проверьте с помощью сайта https://www.mail-tester.com, там разберетесь чего как. Дело в том, что сейчас действуют агрессивные анти-спам политики. Часто письма не попадают даже в соответствующую папку на принимающем сервере, а просто молча удаляются им. Почитайте, у меня есть по этому поводу статья: https://www.wisereport.ru/nonspam-emails
Спасибо! Всё четко работает. Сделал форму без всплывающей формы и на опенкарт 1.5 Код подошёл
Не подскажите, как удалось сделать для Opencart 1.5 ?
Пытаюсь добавить пару полей в форму.
Добавил в скрипте в post_data ‘company’ : $(‘input[name=company]’).val(),
добавил в php $company = $this->request->post[‘company’];
но при добавлении переменной $company в теле письма — там пусто 🙁
Можете подсказать?
Notice: Array to string conversion in /home/mp2016/master-prof.com/www/system/library/mail.php on line 31Notice: Array to string conversion in /home/mp2016/master-prof.com/www/system/library/mail.php on line 36Notice: Error: Could not load mail adaptor Array! in /home/mp2016/master-prof.com/www/system/library/mail.php on line 36
Подскажите что не так? Все по инструкции сделал. OC Version 3.0.3.2 (rs.2)