В этой статье рассмотрим, как создать форму обратной связи для сайта, используя HTML, Bootstrap, JavaScript (jQuery) и PHP.

Особенности контактной формы: работа без перезагрузки страницы (ajax), наличие защиты (капчи), возможность прикрепления к ней файлов, отправка данных на почту (отправленные файлы приходят на почту посредством вложений).

Что такое форма обратной связи?

Форма обратной связи (контактная форма, feedback form, contact form) - это HTML-форма для сайта, посредством которой администратор или менеджер сайта может получать данные от пользователей. Другими словами это один из способов, с помощью которого одни пользователи (посетители сайта) могут взаимодействовать с другими пользователями (менеджерами или администраторами сайта).

Загрузка формы обратной связи

Готовую ajax форму обратной связи с капчей и возможностью прикрепления к ней файлов (текущая версия 1.1) можно скачать с Яндекс хранилища или GitHub.

Посмотреть проект на Github (1.1.1) Скачать архив с Яндекса (1.1.1) Скачать архив с Яндекса (1.1) - 2 формы

Скриншоты формы обратной связи версии 1.1:

Внешний вид формы обратной связи
Внешний вид формы обратной связи
Отображение ошибок во время проверки формы обратной связи
Отображение ошибок во время проверки формы обратной связи
Отображение информации об успешной отправки формы обратной связи
Отображение информации об успешной отправки формы обратной связи

Форму обратной связи версии 1.0 можно загрузить по следующей ссылке: Feedback Form v1.0

Скриншоты формы обратной связи 1.0:

Примечание: файлы имеют кодировку UTF-8 без BOM.

Для проверки работоспособности формы в Денвере необходимо в корне сайта создать файл .htaccess и добавить в него строчку: AddDefaultCharset UTF-8.

Принцип работы AJAX формы обратной связи

В этом разделе рассмотрим, из каких частей состоит AJAX контактная форма для сайта, и также то, как эти части связанные между собой и какие функции они выполняют.

Форму обратной связи, работающей без перезагрузки страницы, структурно можно представить посредством следующей схемы:

Структурная схема формы обратной связи c AJAX

Как видно из схемы форма обратной связи состоит из 2 основных частей: клиентской и серверной.

Клиентская часть формы обратной связи состоит из следующих файлов:

  • index.html - веб-страница, содержащая html5 форму обратной связи);
  • main.js - JavaScript сценарий, который подключен к странице index.html);
  • bootstrap.min.css, bootstrap.min.js - CSS стили и JavaScript плагин Bootstrap;
  • jquery.jgrowl.min.css, jquery.jgrowl.min.js - CSS стили и jQuery-плагин для отображения всплывающих сообщений;
  • jquery-3.1.1.min.js - библиотека jQuery, необходимая для работы js-плагина Bootstrap, jGrowl и скрипта main.js;
  • glyphicons-halflings-regular.eot и др. - файлы, содержащие иконки.

Клиентская часть формы обратной связи

В качестве примера, возьмём HTML форму, состоящую из:

  • 3 полей (имя пользователя, адрес email и область для ввода сообщения);
  • блока, содержащего элементы input с type="file" (для прикрепления файлов к HTML форме);
  • капчи (защита формы от спама);
  • кнопки, отправляющей её на сервер.

Но это не означает то, что вы ограничены только этими элементами. При необходимости вы можете добавить в форму обратной связи новые элементы и удалять существующие.

Сценарий JavaScript (main.js) используется для отправки контактной формы на сервер посредством технологии AJAX (т.е. без перезагрузки страницы). Но кроме отправки, js-сценарий выполняет ещё следующие вспомогательные операции:

  • проверку полей формы на корректность их заполнения (валидацию). Данное действие выполняется с помощью HTML5 функции checkValidity.
  • отображение ошибок валидации (с помощью окрашивания соответствующих полей формы в красный цвет) и отображения всплывающих сообщений;
  • вывод в блоке (в котором пользователь осуществляет прикрепление файлов к форме) информации о количестве файлов, сообщения о соответствии выбранных файлов установленным критериям, а также для добавления новых элементов input с type="file";
  • обновление кода капчи (получение нового изображения с сервера);
  • получение ответа от сервера и его отображение на странице.

Подготовку данных формы для отправки осуществляется на базе HTML5 объекта FormData. Информация, собранная с помощью FormData отправляется на сервер посредством метода POST.

Серверная часть формы обратной связи состоит из следующих файлов:

  • captcha.php - скрипт для генерации капчи;
  • oswald.ttf - шрифт, с помощью которого код капчи пишется на изображении;
  • background.png - изображение, на которое будет наложен текст капчи;
  • process.php - скрипт, выполняющий отправку данных формы на email;
  • PHPMailerAutoload.php - автозагрузчик библиотеки PHPMailer.

Серверная часть формы обратной связи

PHP скрипт process.php выполняет следующие функции:

  • проверяет метод, с помощью которого был послан запрос;
  • выполняет проверку (валидацию) полей формы на стороне сервера;
  • проверяет правильность введённого пользователем кода капчи;
  • проверяет, соответствуют ли полученные файлы установленным характеристикам;
  • перемещает файлы, отправленные пользователем, под уникальными именами в заданную директорию на сервере;
  • в случае успешного выполнения всех вышеприведённых действий отправляет письмо (email) с вложениями по указанному адресу. Кроме отправки письма можно также установить, чтобы данный скрипт сохранял ещё переданные ему данные в файл.
  • отправляет клиенту (браузеру пользователя) результат (ответ) в формате JSON, содержащий сведения об успехе или возникшие ошибки при обработке формы.

Установка и настройка формы обратной связи

Установка формы обратной связи на сервер осуществляется путём копирования папки feedback в корневую директорию сайта.

После копирования контактная форма на сайте с доменным именем sitename будет доступна адресу: sitename/feedback/.

По умолчанию данная форма отправляет данные на адрес email, указанный пользователем в файле process.php.

Настройка email адреса получателя и других параметров письма осуществляются в process.php на следующих строчках:

$mail->From = 'email@mysite.ru'; // email адрес отправителя
$mail->FromName = 'Имя сайта'; // имя отправителя
$mail->Subject = 'Сообщение с формы обратной связи'; // тема письма
$mail->AddAddress('myemail@mail.ru'); // email адрес получателя

По умолчанию файлы, добавленные к форме, приходят на почту в виде вложений.

Кроме этого, php скрипт можно настроить так, чтобы он не прикреплял файлы к письму, а добавлял их в тело с помощью ссылок. Чтобы это осуществить, достаточно в файле process.php удалить 1 блок и добавить 2 позицию 2 следующий:

// место для установки 2 блока, который предназначен для отправки файлов в теле письма посредством ссылок
if (isset($files)) {
  $output .= "Файлы: " . "\n";
  foreach ($files as $value) {
    $href = substr($value,strpos($value, '/feedback/'));
    $output .= '<p><a href="'.$href.'">'.$href.'</a></p>' . "\n";
  }
}

Кроме отправки формы на email, её можно также настроить так, чтобы она отправляла данные в файл.

Осуществляется это посредством добавления в файл process.php в позицию 3 следующих строк:

// место для установки 3 блока, который предназначен для сохранения формы в файл
$output = "---------------------------------" . "\n";
$output .= date("d-m-Y H:i:s") . "\n";
$output .= "Имя пользователя: " . $name . "\n";
$output .= "Адрес email: " . $email . "\n";
$output .= "Сообщение: " . $message . "\n";
if (isset($files)) {
  $output .= "Файлы: " . "\n";
  foreach ($files as $value) {
    $output .= $value . "\n";
  }
}
if (file_put_contents(dirname(__FILE__).'/message.txt', $output, FILE_APPEND | LOCK_EX)) {
  $data['result']='success';
} else {
  $data['result']='error';
}

Добавление полей в форму в версии 1.0

Краткая инструкция по добавлению новых полей в форму обратной связи:

  1. Добавить новый элемент в HTML-форму (файл index.html):
    <input type="text" id="id_поля" name="id_поля">
  2. Добавить строчку, которая будет вставлять данные из HTML элемента в объект formData (файл script.js):
    // добавить после строчки 
    // var formData = new FormData();
    // в formData значение 'id_поля'=значение_поля
    formData.append( 'id_поля', $("#id_поля").val() );
  3. Вставить в блок, в котором формируется тело письма, следующий код (файл process.php):
    // если в массиве POST имеется ключ id_поля
    if (isset($_POST[' id_поля'])) {
      // то добавить его код к телу 
      $output .= "Имя_поля: " . "\n" . $POST[' id_поля'] . "\n";
    }
    

Если наоборот, вы хотите удалить некоторое поле из формы, то необходимо найти в файлах index.html, script.js и process.php соответствующие куски кода и удалить.

Основы работы с версией 1.1

Инициализация кода js-скрипта (создание экземпляра объекта ProcessForm) для обработки формы обратной связи:

// feedbackForm - id HTML-формы
var formFeedback = new ProcessForm({idForm:'feedbackForm'});
formFeedback.init();

С помощью функции-конструктора ProcessForm можно обрабатывать любое количество форм. Осуществляется это следующим образом:

// feedbackForm - id первой HTML-формы
var formFeedback = new ProcessForm({idForm:'feedbackForm'});
formFeedback.init();
// contactForm - id второй HTML-формы 
var contactForm = new ProcessForm({idForm:'contactForm'});
contactForm.init();

Установка PHP файла для обработки формы (form) осуществляется с помощью атрибута action.

<form id="feedbackForm" action="/feedback/process-feedback-form.php" enctype="multipart/form-data" novalidate>

Настроить работу сценария JavaScript очень просто. Осуществляется это посредством указания дополнительных ключей.

/* Параметры указываются в виде:
  {
    ключ: значение;
    ключ: значение;
    ...
  }
  idForm - id формы обратной связи (по умолчанию feedbackForm)
  existenceUploadsFile - наличие у формы блока загрузки файлов (по умолчанию true)
  countFiles - количество файлов для загрузки (по умолчанию 5)
  maxSizeFile - максимальный размер файла в байтах (по умолчанию 524288 байт)
  validFileExtensions - допустимые расширения файлов (по умолчанию 'jpg','jpeg','bmp','gif','png')
  existenceCaptcha - наличие у формы капчи (по умолчанию true)
*/

Например, если вам не нужна капча, то необходимо данный блок удалить из HTML формы и установить при создании экземпляра объекта ProcessForm ключ existenceCaptcha со значением false.

var contactForm = new ProcessForm({
  idForm:'contactForm',
  existenceCaptcha: false
});
contactForm.init();

Ошибки валидации сценарий JavaScript отображает посредством всплывающих сообщений jGrowl. При формировании текста ошибки имя поля берётся из атрибута data-name, а текст ошибки из свойства элемента validationMessage.


Добавление новых полей к форме осуществляется очень просто.

Например, для того чтобы добавить номер телефона в форму обратной связи необходимо:

  1. Добавить элемент для ввода телефона в HTML форму:
    <!-- Телефон пользователя -->
    <div class="form-group has-feedback">
      <label for="phone" class="control-label">Введите телефон*:</label>
      <input type="text" name="phone" data-name="Введите телефон*" required="required" class="form-control" value="" minlength="14">
      <span class="glyphicon form-control-feedback"></span>
    </div>
  2. Вставить значение поля в тело письма (файл process.php):
    if (isset($_POST['phone'])) {
      $output .= 'Телефон пользователя: ' . $_POST['phone'];
    }
    

Формирование результата обработки формы на стороне сервера (process.php) осуществляется с помощью переменной $data.

Основной результат обработки формы устанавливается посредством ключа result, который может иметь следующие значения:

  • success - успешная обработка формы на сервере.
  • invalidCaptcha - неверная капча.
  • error - ошибка при проверке формы.

Создание сообщения, содержащей подробную информацию об ошибке осуществляется с помощью этой же переменной ($data), но имеющей в качестве ключа имя HTML элемента:

Например, формирование ошибки для поля email:

$data['email'] = "Указан не корректный адрес email!";

Код основных файлов формы обратной связи:

<!-- Форма обратной связи -->
<form id="feedbackForm" action="/feedback/process-feedback-form.php" enctype="multipart/form-data" novalidate>
  <div class="row">
    <div class="col-sm-6">
      <!-- Имя пользователя -->
      <div class="form-group has-feedback">
        <label for="name" class="control-label">Имя*:</label>
        <input type="text" name="name" data-name="Имя" class="form-control" required="required" value="" placeholder="" minlength="2" maxlength="30">
        <span class="glyphicon form-control-feedback"></span>
      </div>
    </div>
    <div class="col-sm-6">
      <!-- Email пользователя -->
      <div class="form-group has-feedback">
        <label for="email" class="control-label">Адрес email*:</label>
        <input type="email" name="email" data-name="Адрес email" class="form-control" required="required" value="" placeholder="" maxlength="30">
        <span class="glyphicon form-control-feedback"></span>
      </div>
    </div>
  </div>
  <!-- Сообщение пользователя -->
  <div class="form-group has-feedback">
    <label for="message" class="control-label">Введите сообщение*:</label>
    <textarea name="message" data-name="Сообщение" class="form-control" rows="3" placeholder="Введите сообщение (не менее 20 символов)" minlength="20" maxlength="500" required="required"></textarea>
  </div>
  
  <!-- Блок для прикрепления файлов к форме -->
  <div class="form-group">
    <p style="font-weight: 700;">Прикрепить к сообщению файлы (до <span class="countFiles"></span>):</p>
    <input type="file" name="images[]">
    <p style="margin-top: 3px; margin-bottom: 3px; color: #ff0000;"></p>
  </div>
  
  <hr style="margin-top: 3px; margin-bottom: 3px;">
  <!-- Капча -->              
  <div class="captcha">
    <img class="img-captcha" src="/feedback/captcha/captcha.php" data-src="/feedback/captcha/captcha.php">
    <div class="btn btn-default refresh-captcha"><i class="glyphicon glyphicon-refresh"></i> Обновить</div>
    <div class="form-group has-feedback">
      <label for="captcha" class="control-label">Пожалуйста, введите проверочный код:</label>
      <input name="captcha" data-name="Код на изображении" type="text" class="form-control" required="required" value="" minlength="6" maxlength="6" autocomplete="off">
      <span class="glyphicon form-control-feedback"></span>
    </div>
  </div>
  
  <button name="send-message" type="submit" class="btn btn-primary pull-right">Отправить сообщение</button>
</form>
//после загрузки веб-страницы
$(function () {

  var ProcessForm = function(parameters){
    
    // id формы обратной связи
    this.idForm = parameters['idForm'] || 'feedbackForm';
    // наличие у формы блока загрузки файлов
    this.existenceUploadsFile = parameters['existenceUploadsFile'] || true;
    // наличие у формы капчи
    this.existenceCaptcha = parameters['existenceCaptcha'] || true;    
    // количество элементов input для загрузки файлов
    this.countFiles = parameters['countFiles'] || 5;
    // максимальный размер файла для загрузки (по умолчанию 512 Кбайт)
    this.maxSizeFile = parameters['maxSizeFile'] || 524288;
    // допустимые разрешения файлов
    this.validFileExtensions = parameters['validFileExtensions'] || ['jpg','jpeg','bmp','gif','png'];
    // инициализация
    this.init = function(){
      // получаем форму
      var submitForm = document.getElementById(this.idForm);
      // отправка формы 
      $(submitForm).submit($.proxy(this.submitForm, this));
      if (this.existenceCaptcha) {      
        // обновление капчи
        $(submitForm).find('.refresh-captcha').click($.proxy(this.refreshCaptcha,this));
      }
      if (this.existenceUploadsFile) {
        $('#'+this.idForm+' .countFiles').text(this.countFiles);
        // добавление нового элемента input с type="file"
        $(document).on('change','#' + this.idForm + ' input[name="images[]"]',$.proxy(this.changeInputFile,this));
      }
    };
  }
  // метод, возвращающий результат проверки расширения файла допустимому
  ProcessForm.prototype.validateFileExtension = function(filename) {
    // получаем расширение файла
    var fileExtension = filename.slice((filename.lastIndexOf(".") - 1 >>> 0) + 2);
    // если есть расширение, то проверяем соотвествует ли оно допустимому
    if (fileExtension) {
      for (var i=0; i <= this.validFileExtensions.length; i++) {
        if (this.validFileExtensions[i] == fileExtension) {
          return true;
        }
      }
    }
    return false;
  };
  // валилация формы
  ProcessForm.prototype.validateForm = function() {
    var _this = this;
    var validForm = true;
    var submitForm = document.getElementById(this.idForm);
    $(submitForm).find('input,textarea').each(function() {
      if (this.checkValidity()) {
        _this.changeStateInput(this,'success');
      } else {
        _this.changeStateInput(this,'error');
        $.jGrowl('Поле: "<strong>'+$(this).attr('data-name')+'</strong>"<br>'+this.validationMessage,{theme: 'jgrowl-error', life: 5000});
        validForm = false;
      }
    });
    return validForm;
  };
  // изменение состояния валидация элемента формы
  ProcessForm.prototype.changeStateInput = function(input,state) {
    var input = $(input);
    inputGroup = input.parents('.form-group');
    glyphiconInput = inputGroup.find('.form-control-feedback');
    if (state == 'error') {
      inputGroup.removeClass('has-success').addClass('has-error');
      if (input.prop("tagName").toLowerCase()!='textarea') {
        glyphiconInput.removeClass('glyphicon-ok').addClass('glyphicon-remove');
      }        
    } else if (state=='success') {
      inputGroup.removeClass('has-error').addClass('has-success');
      if (input.prop("tagName").toLowerCase()!='textarea') {        
        glyphiconInput.removeClass('glyphicon-remove').addClass('glyphicon-ok');
      }          
    } else {
      inputGroup.removeClass('has-success has-error');
      glyphiconInput.removeClass('glyphicon-ok glyphicon-remove');
    }
  };
  // обработка изображений для FormData
  ProcessForm.prototype.changeStateImages = function(state) {
    if (!this.existenceUploadsFile) {
      return;
    }
    var submitForm = document.getElementById(this.idForm); 
    var files = $(submitForm).find('[name="images[]"]');
    for (var i = 0; i < files.length; i++) {
      // получить список файлов элемента input с type="file"
      var fileList = files[i].files;
      // если элемент не содержит файлов, то перейти к следующему
      if (fileList.length > 0) {
        // получить первый файл из списка
        var file = fileList[0];
        // проверить тип файла и размер
        if (!((this.validateFileExtension(file.name)) && (file.size < this.maxSizeFile))) {
          $(files[i]).prop('disabled', state);
        }
      } else {
        $(files[i]).prop('disabled', state);
      }
    }
  };
  ProcessForm.prototype.collectData = function() {
    this.changeStateImages(true);
    this.dataForm = new FormData(document.getElementById(this.idForm));
    this.changeStateImages(false);
  };
  // отправка формы
  ProcessForm.prototype.submitForm = function (e) {
    var _this = this;
    e.preventDefault();
    if (this.validateForm()===false) {
      return;
    };
    this.collectData();
    $.ajax({
      type: "POST",
      url: $('#' + _this.idForm).attr('action'),
      data: _this.dataForm,
      contentType: false,
      processData: false,
      cache: false,
      success: function (data) {
        var data =  JSON.parse(data);
        //устанавливаем элементу, содержащему текст ошибки, пустую строку
        $('#' + _this.idForm + '.error').text('');
        // если сервер вернул ответ success, то значит двнные отправлены
        if (data.result == "success") {
          $.jGrowl('Форма успешно отправлена!',{theme: 'jgrowl-success', life: 5000});
          document.getElementById(_this.idForm).reset();
          $('#' + _this.idForm).find('input,textarea').each(function() {
            _this.changeStateInput(this,'clear');
          });
          if (_this.existenceUploadsFile) {
            $('#'+_this.idForm+' .countFiles').parents('.form-group').html(
              '<p style="font-weight: 700;">Прикрепить к сообщению файлы (максимум <span class="countFiles">'+
              _this.countFiles+'</span>):</p><input type="file" name="images[]">'+
              '<p style="margin-top: 3px; margin-bottom: 3px; color: #ff0000;"></p>');
          }
          if (_this.existenceCaptcha) {      
            _this.refreshCaptcha();
          }
        }
        else if (data.result == 'invalidCaptcha') {
          // если сервер вернул ответ invalidcaptcha...
          $.jGrowl('<strong>Внимание:</strong><br>Не верно был введён проверочный код!',{theme: 'jgrowl-error', life: 5000});
          captcha = $('#'+_this.idForm).find('[name="captcha"]').eq(0);
          _this.changeStateInput(captcha,'error');
          $(captcha).val('');
          var imgCaptcha = $('#'+_this.idForm).find('.img-captcha');
          imgCaptcha.attr('src', imgCaptcha.attr('data-src')+'?id=' + Math.random() + '');
        } else {
          // если сервер вернул ответ error...
          $.jGrowl('<strong>Ошибка!</strong><br>Форму не удалось отправить.',{theme: 'jgrowl-warning', life: 5000});
          // отображаем все ошибки
          for (var error in data) {
            if (error == 'result') {
              continue;
            }
            $.jGrowl(data[error],{theme: 'jgrowl-error', life: 5000});
            _this.changeStateInput($('#'+_this.idForm).find('[name="'+error+'"]').eq(0),'error');         
          }
        }
      },
      error: function (request) {
        $.jGrowl('Произошла ошибка ' + request.responseText + ' при отправке данных.',{theme: 'jgrowl-error', life: 5000});
      }
    });      
  };
  // обновление капчи
  ProcessForm.prototype.refreshCaptcha = function() {
    var imgCaptcha = $('#'+this.idForm).find('.img-captcha');
    imgCaptcha.attr('src', imgCaptcha.attr('data-src')+'?id=' + Math.random() + '');
  };
  // изменение элемента input с type="file"
  ProcessForm.prototype.changeInputFile = function(e){
    // условие для добавления нового элемента input с type="file"
    if ((e.currentTarget.files.length > 0) && ($(e.currentTarget).next('p').next('input[name="images[]"]').length==0) && ($('#' + this.idForm + ' input[name="images[]"]').length < this.countFiles)) {
      $(e.currentTarget).next('p').after('<input type="file" name="images[]"><p style="margin-top: 3px; margin-bottom: 3px; color: #ff0000;"></p>');
    }
    // если файл выбран, то выполняем следующие действия...
    if (e.currentTarget.files.length > 0) {
      // получим файл
      var file = e.currentTarget.files[0];
      // проверим размер и расширение файла
      if (file.size > this.maxSizeFile) {
        $(e.currentTarget).next('p').text('*Файл не будет отправлен, т.к. его размер больше '+ this.maxSizeFile/1024 + 'Кбайт');
      } else if (!this.validateFileExtension(file.name)) {
        $(e.currentTarget).next('p').text('*Файл не будет отправлен, т.к. его тип не соответствует разрешённому');
      } else {
        if ($(e.currentTarget).next('p')) {
          $(e.currentTarget).next('p').text('');
        }
      }
    } else {
      // если после изменения файл не выбран, то сообщаем об этом пользователю
      $(e.currentTarget).next('p').text('* Файл не будет отправлен, т.к. он не выбран');
    }
  };
    
  // Подключаем формы для обработки:
  var formFeedback = new ProcessForm({idForm:'feedbackForm'});
  formFeedback.init();
  
  var contactForm = new ProcessForm({idForm:'contactForm',existenceUploadsFile:false,existenceCaptcha: false});
  contactForm.init();

});
<?php
  session_start(); // открываем сессию
  $data['result']='error';   // переменная в которую будем сохранять результат работы
  $allowedExtension = array("jpg" => "image/jpg", "jpeg" => "image/jpeg", "gif" => "image/gif", "png" => "image/png");  // разрешённые типы файлов
  $pathToFile = $_SERVER['DOCUMENT_ROOT'].'/feedback/uploads/'; // директория для хранения файлов
  $maxSizeFile = 524288; // максимальный размер файла

  // функция для проверки длины строки
  function validStringLength($string,$min,$max) {
    $length = mb_strlen($string,'UTF-8');
    if (($length<$min) || ($length>$max)) {
      return false;
    }
    else {
      return true;
    }
  }

  if ($_SERVER['REQUEST_METHOD'] == 'POST') {   // если данные были отправлены методом POST, то...
    $data['result']='success';
    //получить имя, которое ввёл пользователь
    if (isset($_POST['name'])) {
      $name = $_POST['name'];
      if (!validStringLength($name,2,30)) {
        $data['name']='Поля имя содержит недопустимое количество символов.';   
        $data['result']='error';     
      }
    } else {
      $data['result']='error';
    } 
    //получить email, которое ввёл пользователь
    if (isset($_POST['email'])) {
      $email = $_POST['email'];
      if (!filter_var($email,FILTER_VALIDATE_EMAIL)) {
        $data['email']='Поле email введено неправильно';
        $data['result']='error';

      }
    } else {
      $data['result']='error';
    }   
    //получить сообщение, которое ввёл пользователь
    if (isset($_POST['message'])) {
      $message = $_POST['message'];
      if (!validStringLength($message,20,500)) {
        $data['message']='Поле сообщение содержит недопустимое количество символов.';     
        $data['result']='error';   
      }      
    } else {
      $data['result']='error';
    } 
    //получить капчу, которую ввёл пользователь
    if (isset($_POST['captcha'])) {
      $captcha = $_POST['captcha'];
    } else {
      $data['result']='error';
    } 
    // если не существует ни одной ошибки, то продолжаем... 
    if ($data['result']=='success') {
      
      // если пользователь ввёл правильный код капчи, то...
      if ($_SESSION["code"] == $captcha) {

        //обработаем файлы, загруженные пользователем посредством элементов с name="images[]"

        // если ассоциатианый массив $_FILES["images"] существует, то
        if(isset($_FILES["images"])) {
          // переберём все файлы (изображения)
          $files = array();
          foreach ($_FILES["images"]["error"] as $key => $error) {
            // если ошибок не возникло, т.е. файл был успешно загружен на сервер, то...
            if ($error == UPLOAD_ERR_OK) {
              // имя файла на устройстве пользователя
              $nameFile = $_FILES['images']['name'][$key];
              // расширение загруженного пользователем файла в нижнем регистре
              $extFile = mb_strtolower(pathinfo($nameFile, PATHINFO_EXTENSION));
              // размер файла
              $sizefile = $_FILES['images']['size'][$key];
              //myme-тип файла
              $filetype = $_FILES['images']['type'][$key]; 
              // проверить расширение файла, размер файла и mime-тип
              if (!array_key_exists($extFile, $allowedExtension)) {
                $data['files']='Ошибка при загрузке файлов (неверное расширение).';
                $data['result']='error';
                      $data['result7']='error';
              } elseif ($sizefile > $maxSizeFile) {
                $data['files']='Ошибка при загрузке файлов (размер превышает 512Кбайт).';
                $data['result']='error';
                $data['result8']='error';
              } elseif (!in_array($filetype, $allowedExtension)){
                $data['files']='Ошибка при загрузке файлов (неверный тип файла).';
                $data['result']='error';
                $data['result9']='error';
              } else {
                //ошибок не возникло, продолжаем...
                 
                // временное имя, с которым принятый файл был сохранён на сервере
                $tmpFile = $_FILES['images']['tmp_name'][$key];
                // уникальное имя файла
                $newFileName = uniqid('img_', true).'.'.$extFile;
                // полное имя файла
                $newFullFileName = $pathToFile.$newFileName;
                // перемещаем файл в директорию
                if (!move_uploaded_file($tmpFile, $newFullFileName)) {
                  // ошибка при перемещении файла
                  $data['files']='Ошибка при загрузке файлов.';                
                  $data['result']='error';
                  $data['result10']='error';
                } else {
                  $files[] = $newFullFileName;
                }
              }
            } else {
              //ошибка при загрузке файл на сервер
              $data['result']='error';
              $data['result11']='error';
            }
          }
        }
      } else {
        // пользователь ввёл неправильную капчу
        $data['result']='invalidCaptcha';
      }
    }
  } else {
    //ошибка не существует ассоциативный массив $_POST["send-message"]
    $data['result']='error';
    $data['result12']='error';
  }

  // дальнейшие действия (ошибок не обнаружено)
  if ($data['result']=='success') {
    //место для установки 3 блока, который предназначен для сохранения формы в файл
    $output = "---------------------------------" . "\n";
    $output .= date("d-m-Y H:i:s") . "\n";
    $output .= "Имя пользователя: " . $name . "\n";
    $output .= "Адрес email: " . $email . "\n";
    $output .= "Сообщение: " . $message . "\n";
    if (isset($files)) {
      $output .= "Файлы: " . "\n";
      foreach ($files as $value) {
         $output .= $value . "\n";
      }
    }
    if (file_put_contents(dirname(__FILE__).'/info/message.txt', $output, FILE_APPEND | LOCK_EX)) {
      $data['result']='success';
    } else {
      $data['result']='error';         
    } 
    //2. Отправляем на почту

    // включить файл PHPMailerAutoload.php
    require_once dirname(__FILE__) . '/phpmailer/PHPMailerAutoload.php';

    //формируем тело письма
    $output = "Дата: " . date("d-m-Y H:i") . "\n";
    $output .= "Имя пользователя: " . $name . "\n";
    $output .= "Адрес email: " . $email . "\n";
    $output .= "Сообщение: " . "\n" . $message . "\n";

    // место для установки 2 блока, который предназначен для отправки файлов в теле письма посредством ссылок

    
    // создаём экземпляр класса PHPMailer
    $mail = new PHPMailer;
  
    $mail->From      = 'email@mysite.ru';
    $mail->FromName  = 'Имя сайта';
    $mail->Subject   = 'Сообщение с формы обратной связи';
    $mail->Body      = $output;
    $mail->AddAddress( 'myemail@mail.ru' );

    // 1 - блок для прикрепления файлов к письму 
    if (isset($files)) {
      foreach ($files as $value) {
         $output .= $value . "\n";
         $mail->addAttachment($value);
      }
    }
    // конец 1 блока
 
    // отправляем письмо
    if ($mail->Send()) {
      $data['result']='success';
    } else {
      $data['result']='error';
    }
  }

  echo json_encode($data);

?>
<?php
//открывает сессию
session_start();
 
//присваивает PHP переменной captchastring строку символов
$captchastring = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyz';
//получает первые 6 символов после их перемешивания с помощью функции str_shuffle
$captchastring = substr(str_shuffle($captchastring), 0, 6);
//инициализация переменной сессии с помощью сгенерированной подстроки captchastring,
//содержащей 6 символов
$_SESSION["code"] = $captchastring;
 
//Генерирует CAPTCHA
 
//создает новое изображение из файла background.png 
$image = imagecreatefrompng(dirname(__FILE__).'/background.png');
//устанавливает цвет (R-200, G-240, B-240) изображению, хранящемуся в $image
$colour = imagecolorallocate($image, 200, 240, 240);
//присваивает переменной font название шрифта
$font = dirname(__FILE__).'/oswald.ttf';
//устанавливает случайное число между -10 и 10 градусов для поворота текста 
$rotate = rand(-10, 10);
//рисует текст на изображении шрифтом TrueType (1 параметр - изображение ($image), 
//2 - размер шрифта (18), 3 - угол поворота текста ($rotate), 
//4, 5 - начальные координаты x и y для текста (18,30), 6 - индекс цвета ($colour),
//7 - путь к файлу шрифта ($font), 8 - текст ($captchastring) 
imagettftext($image, 18, $rotate, 28, 32, $colour, $font, $captchastring);
//будет передавать изображение в формате png
header('Content-type: image/png');
//выводит изображение
imagepng($image);
?>