[СДЕЛАЙ САМ] Личный кабинет своими руками. Редактирование персональных данных.

После того как авторизация и регистрация сделаны нужно дать возможность пользователям редактировать свои персональные данные. Для этого создаем ресурс Редактирование данных, в котором вызываем AjaxForm
{'!AjaxForm' | snippet:[
            'snippet' =>'custProfileEdit',
            'form' =>'@FILE chunks/forms/editProfileForm.html',
            'customValidators' => 'checkFileInForm',
            'validate'=>'fullname:required:minLength=^2^,
            photos:checkFileInForm,
            password:required:minLength=^8^,
            password_confirm:password_confirm=^password^,
            email:required:email'
            'placeholderPrefix' =>'edit.' 
]}
'placeholderPrefix' =>'edit.' это обязательный параметр в моём случае, т.к. в js я использую split и беру 3-ий элемент.
Валидацию полей обеспечивает сниппет FormIt, поэтому доступны все варианты валидации предусмотренные в этом компоненте. Плюс я использовал кастомный валидатор checkFileInForm для проверки файлов на размер и тип.
<?php
// инициализируем переменную output, отвечающую за результат работы валидатора, со значением true
$output = true;
// разрешённые расширения файлов
$allowedExt = explode(',',$modx->getOption('types_allow_for_users'));

// максимальный размер файла (в Mбайт)
$maxFileSize = (int)$modx->getOption('max_file_size') * 1024 * 1024;
if(is_array($_FILES[$key]['name'])){
    for($i = 0; $i < count($_FILES[$key]['name']); $i++){
        // имя файла
        $fileName = basename( $_FILES[$key]['name'][$i]);
        // размер
        $fileSize = filesize( $_FILES[$key]['tmp_name'][$i]);
        // расширение файла
        $fileExt = mb_strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
        // если имя файла не пустое
        if ($fileName != '') {
            if(!in_array($fileExt, $allowedExt)) {
                // файл имеет недопустимый тип
                $errorMsg = 'Файл ' . $fileName . ' должен иметь один из следующих типов: ' . $allowedExt;
                $validator->addError($key, $errorMsg);
                $output = false; // возвращаем false
            }
            if($fileSize > $maxFileSize) {
                // файл имеет размер больше максимального
                $errorMsg = 'Размер файла '. $fileName .' превышает '.$modx->getOption('max_file_size').' Mбайт.';
                $validator->addError($key,$errorMsg);
                $output = false; // возвращаем false
            }
        }
    }
}else{
    // имя файла
    $fileName = basename( $_FILES[$key]['name']);
    // размер
    $fileSize = filesize( $_FILES[$key]['tmp_name']);
    // расширение файла
    $fileExt = mb_strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
    // если имя файла не пустое
    if ($fileName != '') {
        if(!in_array($fileExt, $allowedExt)) {
            // файл имеет недопустимый тип
            $errorMsg = 'Файл ' . $fileName . ' должен иметь один из следующих типов: ' . $allowedExt;
            $validator->addError($key, $errorMsg);
            $output = false; // возвращаем false
        }
        if($fileSize > $maxFileSize) {
            // файл имеет размер больше максимального
            $errorMsg = 'Размер файла '. $fileName .' превышает '.$modx->getOption('max_file_size').' Mбайт.';
            $validator->addError($key,$errorMsg);
            $output = false; // возвращаем false
        }
    } 
}
return $output;

Сама форма выглядит примерно так
{set $user= $.get['user']?:$_modx->user.id}
<form class="jsUserData ajax_form" enctype="multipart/form-data" method="post">
    {set $name = $user | getUserInfo: 'extended.name'}
    {set $lastName = $user | getUserInfo: 'extended.lastname'}
    <input type="hidden" name="fullname" value="{$name~' '~$lastName}">
    <input type="hidden" name="id" value="{$user}">
    <input type="file" name="photos" id="upload-avatar" class="input-file visually-hidden jsFileInput" data-class="visually-hidden" />
    <span class="error error_photos w-100"></span>
    <span class="error error_id w-100"></span>
    <div class="profile-content profile-edit">
        <div class="profile-edit__item">
            <div class="h2-sm">Информация о пользователe</div>
            <div class="box-field flex-wrap">
                <div class="box-field__label">Имя</div>
                <input class="form-control jsInputName" type="text" name="extended[name]" placeholder="Андрей" value="{$name}">
                <span class="error error_fullname w-100"></span>
            </div>
            <div class="box-field">
                <div class="box-field__label">Фамилия</div>
                <input class="form-control jsInputLastName" type="text" name="extended[lastname]" placeholder="Никифоров" value="{$lastName}">
            </div>
            <div class="box-field">
                {set $gender = $user | getUserInfo: 'gender'}
                <div class="box-field__label">Пол</div>
                <select class="js-styled jq-selectbox-sm" name="gender">
                    <option value="1" {$gender== 1 ? 'selected':''}>Мужской</option>
                    <option value="2" {$gender== 2 ? 'selected':''}>Женский</option>
                </select>
            </div>
            <div class="box-field">
                <div class="box-field__label">День рождения:</div>
                <div class="box-field__devider">
                    <input type="text" class="form-control js-datepicker-birth js-datepicker-valid"
                           placeholder="18.12.1988" name="dob" value="{$user | getUserInfo: 'dob' | date: 'd.m.Y'}"/>
                </div>

            </div>
        </div>
        <div class="profile-edit__item">
            <div class="h2-sm">Контактная информация</div>
            <div class="box-field flex-wrap">
                <div class="box-field__label">Email:</div>
                <input class="form-control" type="email" name="email" placeholder="vipslava.s@gmail.com" value="{$user | getUserInfo: 'email'}">
                <label class="error error_email w-100"></label>
            </div>
            <div class="box-field">
                <div class="box-field__label">Телефон:</div>
                <div class="box-field__devider">
                    <input class="form-control js-mask-tel" type="tel" name="phone" placeholder="+7 (925) 257-87-54" value="{$user | getUserInfo: 'phone'}">
                    <div class="box-field box-field_checkbox">
                        <label class="checkbox-element checkbox-element_3">
                            <input type="checkbox" name="extended[showphone]" value="1" {($user | getUserInfo : 'extended.showphone') ?'checked':''}>
                            <span class="check-label">
                                <span class="check"></span>
                                Показать телефон
                            </span>
                        </label>
                    </div>
                </div>

            </div>
            <div class="box-field">
                {set $state = ($user | getUserInfo: 'state') ?: $_modx->runSnippet('detectRegion')}
                <div class="box-field__label">Регион:</div>
                <select class="js-styled jq-selectbox-sm" name="state">
                    {set $regions = ($_modx->config.regions_list | fromJSON)}
                    {foreach $regions as $region}
                    {if $region == $state}
                    <option selected>{$region}</option>
                    {else}
                    <option>{$region}</option>
                    {/if}
                    {/foreach}
                </select>
            </div>
        </div>
        <div class="profile-edit__item">
            <div class="h2-sm">Информация о компании</div>
            <div class="box-field">
                <div class="box-field__label">Название:</div>
                <input class="form-control" type="text" name="extended[company]" value="{$user | getUserInfo: 'extended.company'}">
            </div>
            <div class="box-field">
                <div class="box-field__label">Сфера деятельности:</div>
                <select class="js-styled jq-selectbox-sm" name="extended[category]">
                    {'pdoResources' | snippet:[
                        'parents' => 11,
                        'limit' => 0,
                        'depth' => 0,
                        'tpl' => '@FILE chunks/common/selectItemCategory.html'
                    ]}
                </select>
            </div>
            <div class="box-field">
                <div class="box-field__label">Сайт:</div>
                <input class="form-control" type="text" name="website" value="{$user | getUserInfo: 'website'}">
            </div>
            <div class="box-field">
                <div class="box-field__label">Ваша должность:</div>
                <input class="form-control" type="text" name="extended[position]" value="{$user | getUserInfo: 'extended.position'}">
            </div>
        </div>
        <div class="profile-edit__item">
            <div class="h2-sm">Дополнительная информация</div>
            <div class="box-field">
                <div class="box-field__label">Обо мне:</div>
                <textarea class="form-control" type="text" name="extended[aboutself]" placeholder="Ввести...">{$user | getUserInfo: 'extended.aboutself'}</textarea>
            </div>
            <div class="box-field">
                <div class="box-field__label">Дополнительно:</div>
                <textarea class="form-control" type="text" name="extended[addinfo]" placeholder="Ввести...">{$user | getUserInfo: 'extended.addinfo'}</textarea>
            </div>
            <div class="box-field box-field_checkbox box-field_checkbox_profile">
                <label class="checkbox-element checkbox-element_3">
                    <input type="checkbox" name="extended[showinfo]" value="1" {($user | getUserInfo : 'extended.showinfo') ?'checked':''}>
                    <span class="check-label">
                        <span class="check"></span>
                        Показать анкету
                    </span>
                </label>
            </div>
        </div>
        <div class="profile-edit__item">
            <div class="h2-sm">Уведомления и рассылки</div>
            <div class="box-field">
                <div class="box-field__label">Разрешить:</div>
                <div class="box-field__devider box-field__devider_mailing">
                    <div class="box-field box-field_checkbox">
                        <label class="checkbox-element checkbox-element_3">
                            <input type="checkbox" name="extended[subscribe]" value="1" {($user | getUserInfo : 'extended.subscribe') ?'checked':''}>
                            <span class="check-label">
                                        <span class="check"></span>
                                        Рассылка
                                    </span>
                        </label>
                    </div>
                </div>
            </div>
        </div>
        <div class="profile-edit__item">
            <div class="h2-sm">Привяжите соцсети для мгновенного входа</div>
            <div class="socials socials_login">
                {'!haProfile' | snippet:[
                'profileTpl' => '@INLINE {$providers}',
                'providerTpl' => '@FILE chunks/hybridauth/socialsAuthItem.html',
                'activeProviderTpl' => '@FILE chunks/hybridauth/socialsAuthItemActive.html',
                ]}
            </div>
        </div>
        <div class="profile-edit__item jsPasswordEdit">
            <div class="h2-sm">Изменить пароль</div>
            <div class="box-field flex-wrap">
                <div class="box-field__label">Введите пароль:</div>
                <input class="form-control" type="password" name="password" value="password">
                <span class="error error_password w-100"></span>
            </div>
            <div class="box-field flex-wrap">
                <div class="box-field__label">Повторите пароль:</div>
                <input class="form-control" type="password" name="password_confirm" value="password">
                <span class="error error_password_confirm w-100"></span>
            </div>
        </div>
        <p class="jsMsgSuccess w-100" style="color:green"></p>
        <button class="btn btn_edit jsSaveBtn">Сохранить</button>
    </div>
</form>
$.get['user'] — это нужно на тот случай, если в дальнейшем будет некий админ, который также с фронта должен будет иметь возможность редактировать данные других пользователей. Если это не требуется можно написать так
{set $user= $_modx->user.id}
Поле fullname разбито на два по требованию заказчика, в принципе проще сделать его один не скрытым полем. Однако если заказчик просит, ему не принято отказывать, поэтому чтобы собрать имя и фамилию в одно поле я написал вот такой скрипт
if ($('.jsUserData input[name="fullname"]').length) {
     $('.jsUserData .jsInputName').change(function () {
         $('.jsUserData input[name="fullname"]').val($(this).val() + ' ' + $('.jsUserData .jsInputLastName').val());
      });
     $('.jsUserData .jsInputLastName').change(function () {
         $('.jsUserData input[name="fullname"]').val($('.jsUserData .jsInputName').val() + ' ' + $(this).val());
     });
 }
Он проверяет, есть ли на странице поле fullname, и если есть, то вешает обработчик на поля name и lastname.
Дальше есть ещё один нюанс, нужно как-то отслеживать изменение пароля. С одной стороны хочется чтобы пароль можно было проверять, например, на количество символов, также хорошей практикой является дать пользователю возможность подтвердить введённый пароль, при этом если по умолчанию оставить поле пустым, валидатор FormIt будет ругаться, что значения полей не совпадают. Делать пароль обязательным нельзя, ведь пользователь может и не хотеть менять его, можно было бы сделать его раскрывающимся по кнопке, но тогда AjaxForm возвратит ошибку, т.к. не найдет этого поля, если пользователь не будет менять пароль. В общем я решил по умолчанию вписывать туда слово password, а на сервере, если $_POST['password'] == password, это поле игнорируется. Ну и на случай если пользователь захочет оставить его пустым, я написал вот такой скрипт
if ($('.jsPasswordEdit').length) {
            $('.jsPasswordEdit input').change(function () {
                if (!$(this).val()) {
                    $(this).val('password');
                }
            });
            $('.jsPasswordEdit input[name="password_confirm"]').change(function () {
                if ($('input[name="password"]').val() != $('input[name="password_confirm"]').val()) {
                    $('.error_password_confirm').text('Пароли не совпадают');
                    $('.jsSaveBtn').prop('disabled', true);
                } else {
                    $('.error_password_confirm').text('');
                    $('.jsSaveBtn').prop('disabled', false);
                }
            });
            $('.jsPasswordEdit input[name="password"]').change(function () {
                if ($('input[name="password"]').val().length < 8) {
                    $('.error_password').text('Пароли должен быть не менее 8 символов');
                    $('.jsSaveBtn').prop('disabled', true);
                } else {
                    $('.error_password').text('');
                    $('.jsSaveBtn').prop('disabled', false);
                }
            });

        }
Потом данные уходят на сервер и обрабатываются сниппетом custProfileEdit
<?php
if(!$_POST['id']){
    $placeholders['edit.error.id'] = '<span class="error">Не передан идентификатор пользователя. Изменение данных невозможно.</span>';
    return $AjaxForm->error('Неизвестный пользователь', array('error' => $placeholders));    
}

$result = $modx->runSnippet('FormIt', $scriptProperties);
foreach($modx->placeholders as $key => $ph){
    if(strpos($key, 'edit.error.') === 0){
        $placeholders[$key] = $ph;
    } 
}
if(!count($placeholders)){  
    $user = $modx->getObject('modUser', $_POST['id']);
    $profile = $user->getOne('Profile');
    $extended = $profile->get('extended');
    $_POST['dob'] = strtotime($_POST['dob']);
    if($_POST['password'] != 'password'){
        $user->set('password',$_POST['password']);
        $user->save();
    }
    if(count($_FILES)){
        $modx->runSnippet('removePreviousFiles', array('key' => 'photos'));
        $photo = $modx->runSnippet('loadUserFiles', array('key' => 'photos'));
        $profile->set('photo', str_replace(MODX_BASE_PATH, '',$photo[0]));
    }
    
    foreach($_POST as $key => $value){
        if($key != 'extended'){
            $_POST[$key] = strip_tags($value);
        }else{
            foreach($_POST[$key] as $k => $v){
                $_POST[$key][$k] = strip_tags($v);
            }
        }        
    }
    $_POST['extended'] = array_merge($extended, $_POST['extended']);
    $profile->fromArray($_POST);
    $profile->save();
   
    return $AjaxForm->success('Данные отправлены', array('success' => 'Данные сохранены'));
}else{
    return $AjaxForm->error('В форме содержатся ошибки', array('error' => $placeholders));    
}
Чтобы не плодить много неиспользуемых фотографий, каждый раз старые удаляем removePreviousFiles
<?php
if(!$key){
    $modx->log(1, 'removePreviousFiles: не указано имя поля содержащего файл');
    return false;
};
$id = $_POST['id'];
if($key != 'gallery'){
    $pathToPhoto = MODX_BASE_PATH . $modx->getOption('user_files_path') . $key . '/' . $id . '/';
}else{
    $pathToPhoto = MODX_BASE_PATH . 'assets/images/products/' . $id .'/';
}

if (file_exists($pathToPhoto)) {
    foreach (glob($pathToPhoto.'*') as $file) {
        if(!is_dir($file)){
            unlink($file);
        }
    }
}
Затем загружаем новую аватарку
<?php
if(!$_FILES){return false;}
// Директория для хранения фото пользователей
if(!$key){
    $modx->log(1, 'loadUserPhoto: не указано имя поля содержащего файл');
    return false;
};

$id = $id ?: $_POST['id'];
if($key != 'gallery'){
    $pathToPhoto = MODX_BASE_PATH . $modx->getOption('user_files_path') . $key . '/' . $id . '/';
}else{
    $pathToPhoto = MODX_BASE_PATH . 'assets/images/products/' . $id .'/';
}

$fileBaseName = $fileBaseName ?: $key;
 $output = array();
if(!$pathToPhoto){
    $modx->log(1, 'loadUserPhoto: не указан путь для сохранения файла');
    return false;
};

if(!is_dir($pathToPhoto)){
    if (!mkdir($pathToPhoto, 0777, 1)) {
        $modx->log(1, 'loadUserPhoto: не удалось создать папку ' . $pathToPhoto);
        return false;
    }
}

//$modx->log(1, 'FILES ' . print_r($pathToPhoto,1));
//загружаем фото пользователя
if(!is_array($_FILES[$key]['name'])){
    // Имя файла пользователя 
    $nameFile = $_FILES[$key]['name'];
    // Получить расширение загруженного пользователем файла в нижнем регистре
    $extFile = mb_strtolower(pathinfo($nameFile, PATHINFO_EXTENSION));
    // Временное имя, с которым принятый файл был сохранён на сервере
    $tmpFile = $_FILES[$key]['tmp_name'];
    $error = $_FILES[$key]['error'];
    // Если файл загружен посредством HTTP POST и ошибок в процессе загрузке не возникло, то...
    if (is_uploaded_file($tmpFile) && !$error) {
        // Формируем имя файлу (фото)
        $nameFilePhoto = $fileBaseName.'_'. $id . '_'. date('d-m-Y_H-i-s.') . $extFile;
        // Получаем полное имя файла (фото)
        $fullNameFilePhoto = $pathToPhoto . $nameFilePhoto;
        // Перемещаем временный файл на новое место $fullNameFilePhoto. Если всё прошло успешно, то...
        if (move_uploaded_file($tmpFile, $fullNameFilePhoto)) {
            $output[] = $fullNameFilePhoto;
        }
        else {
            // Записываем в журнал что произошла ошибка при перемещении файла на новое место
            $modx->log(modX::LOG_LEVEL_ERROR, 'Ошибка при перемещении временного файла '.$tmpFile.' на новое место '.$fullNameFilePhoto);
        }
    }
    else {
        // Записываем в журнал что произошла ошибка при загрузке файла
        $modx->log(modX::LOG_LEVEL_ERROR, 'Ошибка при загрузке файла. Код ошибки: '.$_FILES[$key]['error']);
    }
}else{
    //загружаем фото ресурса
    //$modx->log(1, 'FILES COUNT ' .print_r($_FILES['objphoto'],1));
    for ($i = 0; $i < count($_FILES[$key]['name']); $i++ ){
        $nameFile = $_FILES[$key]['name'][$i];
        $extFile = mb_strtolower(pathinfo($nameFile, PATHINFO_EXTENSION));
        $tmpFile =$_FILES[$key]['tmp_name'][$i];
        $error = $_FILES[$key]['error'][$i];
       
        //$modx->log(1, 'FILES ' . print_r($nameFile,1));
        //$modx->log(1, 'FILES i ' . $i);
        if (is_uploaded_file($tmpFile) && !$error) {
            $nameFilePhoto = $i. '_' . $fileBaseName.'_'. $id . '_'. date('d-m-Y_H-i-s.') . $extFile;
            $fullNameFilePhoto = $pathToPhoto . $nameFilePhoto;
            if (move_uploaded_file($tmpFile, $fullNameFilePhoto)) {
                $output[] = $fullNameFilePhoto;
                //$modx->log(1, 'FILES $fullNameFilePhoto ' . $fullNameFilePhoto);
            }else {
                $modx->log(modX::LOG_LEVEL_ERROR, 'Ошибка при перемещении временного файла '.$tmpFile.' на новое место '.$fullNameFilePhoto);
            }
        }else {
            $modx->log(modX::LOG_LEVEL_ERROR, 'Ошибка при загрузке файла. Код ошибки: '.$error);
            return json_encode(['success' => 'false', 'message' =>  'Ошибка при загрузке файла ' . $nameFile]);
        }
    }
}
return $output;
Осталось принять ответ от сервера и показать пользователю
$(document).on('af_complete', function (event, response) {      
        if (!response.data.success) {
            if (typeof response.data.error === "string" || response.data.error instanceof String){
                // это стандартный вывод если это общая ошибка
                AjaxForm.Message.error(response.data.error);
            }else{
                // это ошибки в конкретных полях
                for (let key in response.data.error) {
                    let keys = key.split('.');
                    if($(response.form).find('.error_' + keys[2]).length){
                        $(response.form).find('.error_' + keys[2]).html(response.data.error[key]);
                    }
                }
            }
        } else { 
            // если есть модалка - зкрываем
            if ($(response.data.success).length) {
                $.fancybox.close();
                setTimeout(function () {
                    $.fancybox.open($(response.data.success), {
                        btnTpl: {
                            smallBtn: '<button type="button" data-fancybox-close class="fancybox-button fancybox-close-small"><i class="icon-close"></i></button>'
                        }
                    });
                }, 10);
            } else {
                AjaxForm.Message.success(response.data.success);
                response.success = false;
            }
        }
        response.message = '';//jGrowl - off
    });
Вот как-то так.
Артур Шевченко
06 декабря 2020, 22:40
modx.pro
9
2 666
+10
Поблагодарить автора Отправить деньги

Комментарии: 13

Андрей Степаненко
07 декабря 2020, 10:47
+1
Во время загрузки фото хотя бы MIME у файла проверяй)) А то php файл зальют тебе)
    Артур Шевченко
    07 декабря 2020, 13:03
    0
    Не зальют, там валидатор работает, я забыл его код выложить)))
    Артур Шевченко
    07 декабря 2020, 13:16
    +5
    Если это вашими стараниями я попал в раздел готовых решений, то спасибо. И хочу спросить, если я ещё несколько подобных опусов выложу, никто же будет возражать. Просто это нужно мне самому для памяти и систематизации сделанного и может быть полезно кому-то ещё, поскольку особенно глубоких знаний тут не требуется.
      artem-makarchuk
      09 января 2021, 22:18
      0
      Как с Вами связаться у меня вопрос по msOneclick
      Артур Шевченко
      10 января 2021, 10:35
      0
      Сейчас не понял, а какая связь между мной и msOneClick?))) Это платное дополнение у него на modstore есть поддержка от автора, не лучше ли спросить там? Но если очень хочется спросить именно у меня, то перейдите в мой профиль, там есть кнопка для связи, пишите — отвечу.
        Антон
        23 января 2021, 11:29
        0
        Сделал ЛК по аналогии с вашей инструкцией. Для Загрузки изображений к тикету сделал проверку на количество загружаемых фото меньше 3х нелзя. Проверяю сам, проверка работает. Но иногда проскакивают тикеты с количеством изображений меньше 3х. Голову уже сломал, в чем проблема. В логах висит ошибка:
        core/cache/includes/elements/modsnippet/33.include.cache.php	87
        Ошибка при загрузке файла. Код ошибки: 4
        Сниппет 33 строка 87
        <?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'). '_'. time(). '.'. $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', 245);
                // Устанавливаем высоту изображению
                $phpThumb->setParameter('h', 336);
                // Задаём тип обрезки
                $phpThumb->setParameter('zc', '1');
                // Задём качество изображения
                $phpThumb->setParameter('q', '90');
                // Генерируем уменьшенное изображение. Если действие прошло успешно, то...
                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;
        Сам сниппет проверки количества фото:
        <?php
        $tid = (int)$_POST['tid']; //replaces the line $tid = (int)$value;
        //Query the amount of images in the database table modx_tickets_files
        $q = $modx->newQuery('TicketFile');
        $q->where(array('class' => 'Ticket'));
        $q->andCondition(array('parent' => $tid, 'deleted' => 0, 'createdby' => $modx->user->id), null, 1);
        $img_count = $modx->getCount('TicketFile', $q);
        
        if ($img_count < 3) {
            //Less than 3 images found
            $validator->addError($key,'You must submit at least 3 photos!');
            return false;
        }
        return true;
          Артур Шевченко
          23 января 2021, 11:39
          0
          Так у вас же написано Ошибка при загрузке файлов, т.е. на сервер как бы попадает 3 файла, но с загрузкой одного из них какая-то проблема. Т.е. вам нужно сначала делать проверку на количество, а потом загружать.
            Антон
            23 января 2021, 11:49
            0
            можете как-то подробней расписать, я пытаюсь внести измы в код от программиста, который убежал)
              Артур Шевченко
              23 января 2021, 12:34
              0
              Давайте так, кусок кода который первым вы вставили, загружает фото в профиль пользователя и к Tickets отношения не имеет. Второй кусок кода, проверят сколько файлов уже было загружено к тикету. Компонент Tickets насколько мне известно не генерирует системных событий, т.е. плагин написать как minishop2 не получится, придётся править исходники. Я бы сделал копию файла assets/components/tickets/js/web/default.js, изменил системную настройку tickets.frontend_js и на 265 строке перед отправкой формы вставил бы проверку
              if( document.getElementById('files').files.length < 3){
                                  Tickets.Message.error('Файлов должно быть не меньше 3');
                                  return false;
                                  }
                Антон
                23 января 2021, 13:28
                0
                не сработало. Отключил сниппет проверки количества фото и сделал правки в default.js. Но никакого уведомления 'Файлов должно быть не меньше 3' не появилось и форма не отправилась
      Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
      13