В этой статье рассмотрим какие необходимо осуществить действия в MODX Revolution для того чтобы у авторизированного пользователя появилась возможность устанавливать себе фото.

Подготовительные шаги

Возможность загружать пользователю своё фото рассмотрим на примере ресурса "Редактирование данных". Это означает то, что перед тем как переходить к чтению материала этого урока, желательно познакомиться со статьёй MODX - Login (Личный кабинет пользователя).

Процесс добавления нового функционала в ресурс "Редактирование данных" начнём с простых вещей:

  1. Создания директории photouser в каталоге assets. Данную директорию будем использовать для хранения фото (аватарок) пользователей.
  2. Подготовки изображения, которое будет иметь пользователь пока он не загрузит своё фото. Например, для пользователей не имеющих своей собственной аватарки будем использовать изображение "default.jpg", которое поместим в папку photouser. В качестве разрешения для фото (аватарок) пользователей остановимся на значении 100x100.

MODX Login - Фото пользователя по умолчанию

После выполнения вышеописанных действий перейдём к правке ресурса "Редактирование данных" и создания сниппета для загрузки фото.

Правка ресурса "Редактирование данных"

MODX Login - Редактирование ресурса UpdateProfile


Откроем ресурс "Редактирования данных" и внесём в него следующие изменения:

  1. Добавим в вызов сниппета UpdateProfile параметр preHooks. Параметр preHooks позволит подключить дополнительную функциональность - вызов сниппета loadUserPhoto до выполнения основных действий UpdateProfile.

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

    • загрузки фото пользователя в директорию /assets/photouser;
    • удаления файла, содержащего фото, по запросу пользователя;
    • установки необходимого значения полю photo, перед тем как оно будет записано в профиль. Для задания необходимого значения полю photo будем использовать метод $hook->setValue().

    Примечание. В качестве имени фото пользователя будем использовать следующий шаблон.

    user[idUser].[extFile]
    //[idUser] - id пользователя
    //[extFile] - исходное расширение фото
    Например:
    user1.png - фото пользователя с id=1
    user6.jpg - фото пользователя с id=6
    

    Код вызова сниппета UpdateProfile:

    [[!UpdateProfile? &validate=`fullname:required` &preHooks=`loadUserPhoto`]]
  2. Добавим к элементу form атрибут enctype со значением "multipart/form-data". Это необходимо сделать для того чтобы форму можно было отправлять с вложениями (файлами).

    <form action="[[~[[*id]]]]" method="post" class="form-horizontal" enctype="multipart/form-data">
  3. Добавим в форму элемент input с помощью которого пользователь сможет прикреплять изображение (файл) к форме.

    <!-- Input для загрузки фото пользователя -->
    <div class="form-group">
      <label for="photo" class="col-sm-4 control-label" style="padding-top: 0px; padding-bottom: 6px;">Фото</label>
      <div class="col-sm-8" style="padding-top: 0px; padding-bottom: 6px;">
        <input type="file" id="photo" name="photo" value="[[+photo]]">
      </div>
    </div>
  4. Добавим на страницу перед формой заголовок, который будет выводить изображение пользователя и его имя. Если пользователь не имеет аватарку, то в качестве изображения будет использоваться файл default.jpg. Осуществить это в MODX Revolution можно с помощью фильтра default, который будет проверять содержимое плейсхолдера photo. Если плейсхолдер photo имеет значение, то использовать его. В противном случае выводить картинку по умолчанию.

    <!-- Блок, содержащий фото и имя пользователя -->
    <div id="crop" class="img-rounded center-block" style="height: 100px; width: 100px; overflow: hidden;">
      <img  id="img-photo" src="[[+photo:default=`/assets/photouser/default.jpg`]]">
    </div>
    <p class="lead text-center">[[+fullname]]</p>
    <hr>
  5. Добавим на страницу кнопку (элемент input с type="submit"), который будет удалять фото пользователя.

    <!-- Кнопка "Удалить фото" -->
    <input type="submit" value="Удалить фото" name="delete-photo-btn" id="login-updprof-btn" class="btn btn-primary pull-left">
  6. Напишем скрипт на языке JavaScript с использованием функций библиотеки jQuery. Данный скрипт будет показывать аватар (фото) пользователя ещё до загрузки его на сервер. Функционал скрипта основывается на File API, который появился в HTML5.
    <script>
    // После загрузки страницы
    $(function(){
      // Произошло изменение значения элемента с id = "photo"
      $('#photo').change(function(e){
        // Если браузер не поддерживает FileReader, то ничего не делаем
        if (!window.FileReader) {
          console.log('Браузер не поддерживает File API');
          return;
        }
        // Получаем выбранный файл (изображение)
        var file = e.target.files[0];
        // Выводим сообщение, что браузер не поддерживает указанный тип файла    
        if (!((file.type=='image/png') || (file.type=='image/jpeg'))) {
          $('#img-error').text('Загруженный файл не является изображением');
          return;
        }
        // Выводим сообщение, что файл имеет большой размер 
        if (file.size>=524288) {
          $('#img-error').text('Размер файла больше чем 512Кбайт');
          return;      
        }
        $('#img-error').text('');
        // Создаём экземпляр объекта FileReader, посредством которого будем читать файл
        var reader = new FileReader();
        // После успешного завершения операции чтения файла
        $(reader).on('load', function(event){
        // Указываем в качестве значения атрибута src изображения содержимое файла (картинки) 
        $('#img-photo').attr('src',event.target.result);
        // Изменяем ширину и высоту изображения
          if ($('#img-photo').width()>=$('#img-photo').height()) {
            $('#img-photo').css('height','100px');
            $('#img-photo').css('margin-left',-($('#img-photo').width()-100)/2);
          } 
          else {
            $('#img-photo').css('widht','100px');   
            $('#img-photo').css('margin-top',-($('#img-photo').height()-100)/2); 
          }
        });
        // Запускает процесс чтения файла (изображения). После завершения чтения файла его содержимое будет доступно посредством атрибута result
        reader.readAsDataURL(file);
      });
      // Перед отправкой формы на сервер...
      $("#updateProfile").submit(function(e) {
        // Проверяем значения поля photo. Если оно равно пустой строке, то данные отправляем
        if ($('#photo').val()=='') {
          return;
        }
        // Если элемент содержит некоторую строку (ошибки связанные с фото), то отменяем отправку формы
        if ( (($('#img-error').text()).length>0)) {
          e.preventDefault();
        }
      });  
    });
    </script>

В итоге ресурс "Редактирование данных" будет иметь следующий основный код:

[[!UpdateProfile? &validate=`fullname:required` &preHooks=`loadUserPhoto`]]

<script>
// После загрузки страницы
$(function(){
  // Произошло изменение значения элемента с id = "photo"
  $('#photo').change(function(e){
    // Если браузер не поддерживает FileReader, то ничего не делаем
    if (!window.FileReader) {
      console.log('Браузер не поддерживает File API');
      return;
    }
    // Получаем выбранный файл (изображение)
    var file = e.target.files[0];
    // Выводим сообщение, что браузер не поддерживает указанный тип файла    
    if (!((file.type=='image/png') || (file.type=='image/jpeg'))) {
      $('#img-error').text('Загруженный файл не является изображением');
      return;
    }
    // Выводим сообщение, что файл имеет большой размер 
    if (file.size>=524288) {
      $('#img-error').text('Размер файла больше чем 512Кбайт');
      return;      
    }
    $('#img-error').text('');
    // Создаём экземпляр объекта FileReader, посредством которого будем читать файл
    var reader = new FileReader();
    // После успешного завершения операции чтения файла
    $(reader).on('load', function(event){
    // Указываем в качестве значения атрибута src изображения содержимое файла (картинки) 
    $('#img-photo').attr('src',event.target.result);
    // Изменяем ширину и высоту изображения
      if ($('#img-photo').width()>=$('#img-photo').height()) {
        $('#img-photo').css('height','100px');
        $('#img-photo').css('margin-left',-($('#img-photo').width()-100)/2);
      } 
      else {
        $('#img-photo').css('widht','100px');   
        $('#img-photo').css('margin-top',-($('#img-photo').height()-100)/2); 
      }
    });
    // Запускает процесс чтения файла (изображения). После завершения чтения файла его содержимое будет доступно посредством атрибута result
    reader.readAsDataURL(file);
  });
  // Перед отправкой формы на сервер...
  $("#updateProfile").submit(function(e) {
    // Проверяем значения поля photo. Если оно равно пустой строке, то данные отправляем
    if ($('#photo').val()=='') {
      return;
    }
    // Если элемент содержит некоторую строку (ошибки связанные с фото), то отменяем отправку формы
    if ( (($('#img-error').text()).length>0)) {
      e.preventDefault();
    }
  });  
});
</script>


<div class="container">
  <div class="row">
    <div class="col-md-8 col-lg-6">
      <div class="panel panel-primary">
        <div class="panel-heading"><i class="glyphicon glyphicon-edit"></i> Редактирование данных</div>
        <div class="panel-body">
          <div class="updprof-error">[[+error.message]]</div>
          [[+login.update_success:is=`1`:then=`[[%login.profile_updated? &namespace=`login` &topic=`updateprofile`]]`]]
          <form id="updateProfile" action="[[~[[*id]]]]" method="post" class="form-horizontal" enctype="multipart/form-data">
            <input type="hidden" name="nospam" value="">
            <!-- Блок, содержащий фото и имя пользователя -->
            <div id="crop" class="img-rounded center-block" style="height: 100px; width: 100px; overflow: hidden;">
              <img  id="img-photo" src="[[+photo:default=`/assets/photouser/default.jpg`]]">
            </div>
            <p class="lead text-center">[[+fullname]]</p>
            <hr>
            <div class="form-group">
              <label for="fullname" class="col-sm-4 control-label">[[!%login.fullname? &namespace=`login` &topic=`updateprofile`]]</label>
              <div class="col-sm-8">
                <input type="text" name="fullname" class="form-control" id="fullname" value="[[+fullname]]">
                <span class="help-block text-error">
                  [[+error.fullname]]
                </span>        
              </div>      
            </div>
            <!-- Input для загрузки фото пользователя -->
            <div class="form-group">
              <label for="photo" class="col-sm-4 control-label" style="padding-top: 0px; padding-bottom: 6px;">Фото</label>
              <div class="col-sm-8" style="padding-top: 0px; padding-bottom: 6px;">
                <input type="file" id="photo" name="photo" value="[[+photo]]">
                <span id="img-error" class="help-block text-error"></span>  
              </div>
            </div>
            <div class="form-group">
              <label for="phone" class="col-sm-4 control-label">[[!%login.phone]]</label>
              <div class="col-sm-8">
                <input type="text" name="phone" class="form-control" id="phone" value="[[+phone]]">
                <span class="help-block text-error">
                  [[+error.phone]]
                </span>        
              </div>      
            </div> 
            <div class="form-group">
              <label for="mobilephone" class="col-sm-4 control-label">[[!%login.mobilephone]]</label>
              <div class="col-sm-8">
                <input type="text" name="mobilephone" class="form-control" id="mobilephone" value="[[+mobilephone]]">
                <span class="help-block text-error">
                  [[+error.mobilephone]]
                </span>        
              </div>      
            </div> 
            <div class="form-group">
              <label for="address" class="col-sm-4 control-label">[[!%login.address]]</label>
              <div class="col-sm-8">
                <input type="text" name="address" class="form-control" id="address" value="[[+address]]">
                <span class="help-block text-error">
                  [[+error.address]]
                </span>        
              </div>      
            </div>  
            <div class="form-group">
              <label for="country" class="col-sm-4 control-label">[[!%login.country]]</label>
              <div class="col-sm-8">
                <input type="text" name="country" class="form-control" id="country" value="[[+country]]">
                <span class="help-block text-error">
                  [[+error.country]]
                </span>        
              </div>      
            </div>   
            <div class="form-group">
              <label for="city" class="col-sm-4 control-label">[[!%login.city]]</label>
              <div class="col-sm-8">
                <input type="text" name="city" class="form-control" id="city" value="[[+city]]">
                <span class="help-block text-error">
                  [[+error.city]]
                </span>        
              </div>      
            </div>  
            <div class="form-group">
              <label for="website" class="col-sm-4 control-label">[[!%login.website]]</label>
              <div class="col-sm-8">
                <input type="text" name="website" class="form-control" id="website" value="[[+website]]">
                <span class="help-block text-error">
                  [[+error.website]]
                </span>        
              </div>      
            </div>  
            <!-- Кнопка "Удалить фото" -->
            <input type="submit" value="Удалить фото" name="delete-photo-btn" id="login-updprof-btn" class="btn btn-primary pull-left">
            <!-- Кнопка "Обновить данные" -->
            <input type="submit" value="Обновить данные" name="login-updprof-btn" id="login-updprof-btn" class="btn btn-primary pull-right">
          </form>
        </div>
      </div>
    </div>
  </div>
</div>

Страница "Редактирование данных" будет иметь следующий вид:

MODX Login - Редактирование ресурса UpdateProfile

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

MODX Login - Ошибки при загрузке фото

Создание сниппета для добавления фото

Создадим сниппет loadUserPhoto.

MODX Login - Создание сниппета для загрузки фото пользователя

Вставим в поле код сниппета (php) следующее содержимое:

<?php
// Получить профиль пользователя
$profile = $modx->user->getOne('Profile');
// Получить значение поля photo
$pathToPhoto = $profile->get('photo');
// Установить полю photo текущее значение
$hook->setValue('photo',$pathToPhoto);

// Запрос на удаление картинки (глобальный массив POST имеет ключ delete-photo-btn со значением отличным от NULL)
if (isset($_POST['delete-photo-btn'])) {
  // Если поле photo не пустое, то..
  if ($pathToPhoto) {
    // Сформировать полный путь к файлу (фото)
    $fullPathToPhoto = $modx->config['base_path'].$pathToPhoto;
    // Если файл (фото) есть, то удалить его
    if(file_exists($fullPathToPhoto))
      unlink($fullPathToPhoto);
    // Установить полю photo пустое значение
    $hook->setValue('photo','');
  }
}

// Запрос на обновление (глобальный массив POST имеет ключ login-updprof-btn со значением отличным от NULL)
if (isset($_POST['login-updprof-btn'])) {
  // Допустимые расширения (jpg, png, jpeg)
  $validExt = array('jpg', 'png', 'jpeg');
  // Директория для хранения фото пользователей
  $pathToPhoto = $modx->config['base_path'] . 'assets/photouser/';
  // Имя файла пользователя 
  $nameFile = $_FILES['photo']['name'];
  // Получить расширение загруженного пользователем файла в нижнем регистре
  $extFile = mb_strtolower(pathinfo($nameFile, PATHINFO_EXTENSION));
  // Временное имя, с которым принятый файл был сохранён на сервере
  $tmpFile = $_FILES['photo']['tmp_name'];
  // Если файл загружен посредством HTTP POST и ошибок в процессе загрузке не возникло, то...
  if ((is_uploaded_file($tmpFile)) && !($_FILES['photo']['error'])) {
    // Проверям соответствует ли расширение файла допустимому. Если всё хорошо, то...
    if(in_array($extFile, $validExt)) {
      // Формируем имя файлу (фото)
      $nameFilePhoto = 'user'.$modx->user->get('id') . '.' . $extFile;
      // Получаем полное имя файла (фото)
      $fullNameFilePhoto = $pathToPhoto . $nameFilePhoto;
      // Перемещаем временный файл на новое место $fullNameFilePhoto. Если всё прошло успешно, то...
      if (move_uploaded_file($tmpFile, $fullNameFilePhoto)) {
        // если файл phpthumb.class.php не был подключён, то включить его
        require_once MODX_CORE_PATH.'model/phpthumb/phpthumb.class.php';
        // Создать новый экземпляр класса phpThumb
        $phpThumb = new phpThumb();
        // Указываем исходное изображение
        $phpThumb->setSourceFilename($fullNameFilePhoto);
        // Устанавливаем ширину изображению
        $phpThumb->setParameter('w', 100);
        // Устанавливаем высоту изображению
        $phpThumb->setParameter('h', 100);
        // Задаём тип обрезки
        $phpThumb->setParameter('zc', '1');
        // Задём качество изображения
        $phpThumb->setParameter('q', '80');
        // Генерируем уменьшенное изображение. Если действие прошло успешно, то...
        if ($phpThumb->GenerateThumbnail()) {
          // Сохраняем изображение в файл $fullNameFilePhoto. Если данное действие завершилось успехом, то..
          if ($phpThumb->RenderToFile($fullNameFilePhoto)) {
            // Устанавливаем в поле photo путь к файлу 
            $hook->setValue('photo',$modx->getOption('assets_url'). 'photouser/' . $nameFilePhoto);
          }
          else {
            $modx->log(modX::LOG_LEVEL_ERROR, 'Ошибка при сохрании изображения в файл '.$fullNameFilePhoto);
          }
        }
        else {
          // Записываем полученую ошибку в журнал MODX
          $modx->log(modX::LOG_LEVEL_ERROR, print_r($phpThumb->debugmessages, 1));
        }
      }
      else {
        // Записываем в журнал что произошла ошибка при перемещении файла на новое место
        $modx->log(modX::LOG_LEVEL_ERROR, 'Ошибка при перемещении временного файла '.$tmpFile.' на новое место '.$fullNameFilePhoto);
      }
    } 
    else {
      // Записываем в журнал сообщение о том, что расширение файла не соответствует разрешённому
      $modx->log(modX::LOG_LEVEL_ERROR, 'Изображение имеет недопустимое расширение');
    }
  }
  else {
    // Записываем в журнал что произошла ошибка при загрузке файла
    $modx->log(modX::LOG_LEVEL_ERROR, 'Ошибка при загрузке файла. Код ошибки: '.$_FILES['photo']['error']);
  }
}
return true;

Скачать файлы с гитхаба