Использование prepare-сниппетов в FormLister

На написание топика сподвигло достаточно содержательное обсуждение компонента с paic , по итогам которого я решил прояснить некоторые моменты, связанные с практическим использованием FormLister.


Рассматривать буду на примере формы для регистрации пользователей. Вопросы у paic возникли такие:
  • загрузка фото при регистрации;
  • выбор страны из списка;
  • задание даты рождения пользователя.

Сразу привожу итоговый вызов сниппета:

[!FormLister?
&formid=`register`
&controller=`Register`
&defaults=`{"photo":""}`
&keepDefaults=`photo`
&rules=`{
"fullname":{
    "required":"Обязательно введите имя"
},
"email":{
    "required":"Обязательно введите email",
    "email":"Введите email правильно",
    "custom":{
	"function":"\\FormLister\\Register::uniqueEmail",
	"message":"Этот email уже использует другой пользователь"
    }
},
"password":{
    "required":"Обязательно введите пароль",
    "minLength":{
    	"params":6,
	"message":"В пароле должно быть больше 6 символов"
    }
},
"repeatPassword":{
    "required":"Повторите пароль",
    "equals":{
    	"message":"Пароли не совпадают"
    }
},
"!birthday":{
    "date":{
	"params":"d.m.Y",
	"message":"Введите дату правильно"
    }
},
"agree":{
    "required":"Для регистрации вы должны принять правила"
}
}`
&attachments=`userpic`
&fileRules=`{
"userpic":{
    "optional":"Не удалось загрузить файл",
    "maxSize" : {
    	"params": 1024,
    	"message": "Размер файла не должен превышать 1 мб"
    },
    "images": "Разрешены только картинки"
}
}`
&allowedFields=`email,password,username,fullname,country,photo,dob`
&formControls=`agree`
&prepare=`countryList`
&prepareProcess=`userPhoto,userDob`
&formTpl=`@CODE:
<div class="row">
    <div class="col-md-6 col-md-offset-3">
	<div class="well">
            <form method="post" enctype="multipart/form-data">
		<input type="hidden" name="formid" value="register">
		<div class="form-group[+fullname.errorClass+][+fullname.requiredClass+]">
		    <label for="fullname">* Имя</label>
		    <input type="text" class="form-control" id="fullname" placeholder="Имя" name="fullname" value="[+fullname.value+]">
		    [+fullname.error+]
		</div>
		<div class="form-group[+email.errorClass+][+email.requiredClass+]">
		    <label for="email">* Email</label>
		    <input type="text" class="form-control" id="email" placeholder="Email" name="email" value="[+email.value+]">
		    [+email.error+]
		</div>
		<div class="form-group[+birthday.errorClass+]">
		    <label for="birthday">Дата рождения (дд.мм.гггг)</label>
		    <input type="text" class="form-control" id="birthday" placeholder="Дата рождения" name="birthday" value="[+birthday.value+]">
		    [+birthday.error+]
		</div>
		<div class="form-group">
		    <label for="country">* Cтрана</label>
		    <select name="country" class="form-control">[+countryList+]</select>
		</div>
		<div class="form-group[+userpic.errorClass+]">
		    <label for="userphoto">Картинка</label>
		    <input class="form-control" type="file" name="userpic">
		    [+userpic.error+]
		</div>
		<div class="row">
		    <div class="col-md-6">
		        <div class="form-group[+password.errorClass+][+password.requiredClass+]">
        		    <label for="password">* Пароль</label>
	                    <input type="password" class="form-control" id="password" placeholder="Пароль" name="password" value="">
			    [+password.error+]
			</div>
		    </div>
		    <div class="col-md-6">
			<div class="form-group[+repeatPassword.errorClass+][+repeatPassword.requiredClass+]">
			    <label for="repeatPassword">* Повторите пароль</label>
			    <input type="password" class="form-control" id="repeatPassword" placeholder="Повторите пароль" name="repeatPassword" value="">
			    [+repeatPassword.error+]
			</div>
		    </div>
	        </div>
	        <div class="checkbox[+agree.requiredClass+]">
		    <label>
		        <input type="checkbox" name="agree" value="Да" [+c.agree.Да+]>Я согласен с правилами
		    </label>
		    [+agree.error+]
		</div>
		[+form.messages+]
		<div class="form-group">
		    <button type="submit" class="btn btn-primary btn-block text-center"><i class="glyphicon glyphicon-user"></i> Зарегистрироваться</button>
		</div>
	    </form>
	</div>
    </div>
</div>`
&messagesOuterTpl=`@CODE:<div class="alert alert-danger" role="alert">[+messages+]</div>`
&successTpl=`@CODE:<div>Поздравляем с успешной регистрацией, [+fullname.value+]! Теперь вы можете <a href="[~11~]">авторизоваться</a> на сайте.</div>`
&errorTpl=`@CODE:<span class="help-block">[+message+]</span>`
&errorClass=` has-error`
&requiredClass=` has-warning`
!]


У paic возникло затруднение с параметром allowedFields. В этом параметре указаны поля, которые в данном случае разрешены к использованию для регистрации пользователя. Если параметр не задавать, то все поля отправятся в модель modUsers как есть. Мне кажется, что правильнее ограничивать набор полей, но тут уже вам решать.

По этой же причине заданы параметры defaults и keepDefaults, чтобы пользователь не мог самостоятельно подставить в форму поле photo. В defaults задается пустое значение этого поля, а keepDefaults не даст перезаписать это поле значением из GET или POST.

Теперь по порядку.

Загрузка фото


Сама по себе задача, как мне кажется, не требует решения вообще, потому что зачем заставлять пользователя загружать фото при регистрации? Я бы делал такое в редактировании профиля, это было бы удобнее и мне, как разработчику, и пользователю. Но пусть будет фото при регистрации.

В общем случае задача сводится к тому, чтобы загрузить файл, обязательно проверить, загрузил ли пользователь именно то, что надо, затем сохранить файл куда-то и задать в поле photo путь к файлу перед тем как отправить данные в модель modUsers. Сделать в форме поле input type=«file» name=«photo» не прокатит — если не понятно почему, то дальше можно не читать.

Загрузка файлов и валидация в FormLister есть, загружать будем в поле с именем userpic. Остается только решить вопрос с сохранением файла в нужное место и заданием поля photo. Для этого понадобится написать сниппет userPhoto:

<?php
//получаем массив с загруженными файлами
$files = $FormLister->getFormData('files');
//проверяем, есть ли там нужный файл и загружен ли он успешно
if (isset($files['userpic']) && $files['userpic']['error'] === 0) {
    //задаем папку для хранения пользовательских фото	
    $dir = 'assets/images/userphoto/';
    //получаем имя загруженного файла, без расширения
    $filename = $FormLister->fs->takeFileName($files['userpic']['name']);
    //расширение отдельно
    $ext = $FormLister->fs->takeFileExt($files['userpic']['name']);
    //делаем транслитерацию имени файла, добавляем к нему расширение
    $filename = $modx->stripAlias($filename).'.'.$ext;
    //получаем предполагаемый путь к файлу, при необходимости переименовываем, чтобы не затереть файл с таким же именем
    $filename = $FormLister->fs->getInexistantFilename($dir.$filename,true);
    //пытаемся переместить файл в нужное место
    if ($FormLister->fs->makeDir($dir) && move_uploaded_file($files['userpic']['tmp_name'],$filename)) {
        //если получилось, то сохраняем в поле photo относительный путь к файлу
    	$FormLister->setField('photo',$FormLister->fs->relativePath($filename));
    }
}
?>


Добавляем имя сниппета в параметр prepareProcess. Указанные в этом параметре сниппеты выполнятся после успешной валидации, а до этого момента нет смысла сохранять загруженные файлы.

Теперь можно приложить к форме файл, и если все остальные поля заполнены правильно, то после отправки формы будет создан пользователь с фото. Если файл не приложить, то все равно будет создан, но уже без фото.

Список стран

Очевидный поклонникам Ditto способ вписать в select все нужные страны вполне себе годный, если нужно иметь дело с двумя-тремя странами. Если стран много, то придется написать еще один сниппет, countryList:

<?php
$file = MODX_MANAGER_PATH."/includes/lang/country/{$FormLister->getCFGDef('countryLang','russian-UTF8')}_country.inc.php";
if (!file_exists($file) || !is_readable($file)) $file = MODX_BASE_PATH.MGR_DIR."/includes/lang/country/russian-UTF8_country.inc.php";
$out = '';
include($file);
foreach ($_country_lang as $key => $value) {
    $out .= '<option value="'.$key.'"'.($FormLister->getField('country') == $key ? ' selected' : '').'>'.$value.'</option>';		
};
$FormLister->setPlaceholder('countryList',$out);
?>


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

Может возникнуть вопрос, почему поле задается через $FormLister->setPlaceholder('countryList',$out) вместо $FormLister->setField('countryList',$out). На самом деле можно и так, внешне работать будет одинаково. Разница лишь в том, что заданное через setPlaceholder поле можно использовать только для вывода в шаблоне, передать его в модель уже не получится.

Сниппет указывается в параметре prepare — список стран нам нужен сразу.

Дата рождения

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

Для этого понадобится сниппет userDob:

<?php
$date = $data['birthday'];
$date = $date ? \DateTime::createFromFormat('d.m.Y',$date)->getTimestamp() : 0;
$FormLister->setField('dob',$date);
?>


Добавляем сниппет в параметр prepareProcess и задача решена. Обратите внимание на то, как задано правило для валидации даты: !birthday означает, что пользователь может не указывать дату, тогда и поле dob не будет установлено.

Почему я не стал вводить ограничения, как с полем photo — смысла в этом нет, так как ограничение уже введено правилом валидации, так что в поле dob попадет корректное значение.

Заключение


Таким образом, prepare-сниппеты можно вызывать до валидации данных в форме и после.

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

Так как в качестве prepare-сниппетов можно использовать статические методы класса, то можно написать такой класс для типовых решений и тягать его из проекта в проект.

Для каких-то масштабных задач prepare-сниппеты не годятся и лучше не городить огород, а создать отдельный контроллер.

39 комментариев

avatar
Спасибо автору за это дополнение и за компонент в целом. Теперь действительно можно не заморачиваться на webloginPE, если кому на сайте нужен личный кабинет. И это только по-началу все выглядит большим и сложным, фактически все просто и запускается с пол-оборота.
Отдельное спасибо Pathologic за долготерпение при обсуждении в личке (даже если я и не всегда задавал умные вопросы)))
avatar
Решил и свой скромный пример добавить (по формам регистрации).
Контроллер Register позволяет регистрировать пользователей в заданные группы, для этого есть параметр userGroups.
В документации сказано
userGroups
Добавляет зарегистрированного пользователя в указанные группы.
Возможные значения — имена групп, разделенные запятой.
Значение по умолчанию — пусто.
А если надо разделить по группам? К примеру, заказчиков регистрировать в одну группу, а подрядчиков — в другую, и на сайте у каждой группы личные кабинеты разные.
Не делать же две разные формы регистрации…
Я сделал так в одной форме.
Вызов сниппета:
[!FormLister?
&formid=`register`
&controller=`Register`
&rules=`{
"email":{
	"required":"Обязательно введите email",
	"email":"Введите email правильно",
	"custom":{
		"function":"\\FormLister\\Register::uniqueEmail",
		"message":"Этот email уже использует другой пользователь"
	}
},
"password":{
	"required":"Обязательно введите пароль",
	"minLength":{
		"params":6,
		"message":"В пароле должно быть больше 6 символов"
	}
},
"repeatPassword":{
	"required":"Повторите пароль",
	"equals":{
		"message":"Пароли не совпадают"
	}
},
"category":{
	"required":"Для регистрации вы должны указать свой статус"
}
}`
&prepare=`prepare_groups`
&formControls=`category`
&formTpl=`@CODE:
	<form id="login-1" method="post" action="[~[*id*]~]">
	    <input type="hidden" name="formid" value="register">
            <input type="text" class="form-control" placeholder="Email" name="email" value="[+email.value+]"/>
				[+email.error+]
            <input type="password" class="form-control" placeholder="Пароль" name="password" value=""/>
				[+password.error+]
	    <input type="password" class="form-control" placeholder="Повторите пароль" name="repeatPassword" value="">
				[+repeatPassword.error+]
	    <label class="radio-inline">
		<input type="radio" name="category" value="customer" [+c.category.customer+]> Я заказчик
	    </label>
	    <label class="radio-inline">
		<input type="radio" name="category" value="performer" [+c.category.performer+]> Я подрядчик
	    </label>
				[+category.error+]
				[+form.messages+]
		<button type="submit" class="btn btn-primary"><i class="glyphicon glyphicon-user"></i> Зарегистрироваться</button>
	</form>`
&messagesOuterTpl=`@CODE:<div class="alert alert-danger" role="alert">[+messages+]</div>`
&successTpl=`@CODE:<div>Поздравляем с успешной регистрацией, [+email.value+]! Теперь вы можете <a href="[~3~]">авторизоваться</a> на сайте.</div>`
&errorTpl=`@CODE:<span class="help-block">[+message+]</span>`
&errorClass=` has-error`
&requiredClass=` has-warning`
!]


и к нему prepare-сниппет prepare_groups:
<?php
if ($FormLister->getField('category') == 'performer') {
	$FormLister->config->setConfig(array(
	'userGroups'=>'performer'
	));
} else {
	$FormLister->config->setConfig(array(
	'userGroups'=>'customer'
	));
}
?>

На выходе получилось так:
avatar
В документации ошибка: группы в списке не запятой разделяются, а "||"; не помню уже, почему такой разделитель решил сделать.
avatar
Здравствуйте!
Возникла необходимость сделать возможным для пользователя создавая статью прикреплять заглавное фото и галерею, а при редактировании своих статей иметь возможность удалить неактуальные фото, заменить их на новые — по-возможности удалив старые файлы картинок с сервера.
Еще раз благодарность за архив с примерами — на его основе и провожу тесты. Но, как оказывается, шаг в сторону и увяз…
Есть тв photo тип image, в ктр должна произойти запись пути загруженной картинки, определенного в сниппе userPhoto — взят с этой страницы. По замыслу это заглавное фото статьи — для списков и пр.
Но в итоге запись не делается
в дебаге в пункте Prepare to validate files массив [userpic] => Array содержит приложенную картинку, но в пункте Create record для [photo] => пусто

Я не большой спец… поэтому стучусь иногда) — понимаю так, что при вызове формлистера либо чего-то нет, либо что-то лишнее)) поэтому приведу его ниже
[!FormLister?
&controller=`Content`
&formid=`article`
&clearCache=`1`
&submitLimit=`10`
&debug=`1`
&rules=`{
«pagetitle»:{
«required»:«Обязательно введите заголовок»,
«minLength»:{
«params»:10,
«message»:«Должно быть не менее 10 символов»
}
},
«content»:{
«required»:«Статья не может быть без текста»,
«minLength»:{
«params»:100,
«message»:«Должно быть не менее 100 символов»
}
},
«parent»:{
«in»:{
«message»:«Выберите раздел»
}
}
}`
&defaultsSources=`param:contentDefaults`
&contentDefaults=`{
«template»:13,
«published»:1
}`
&defaults=`photo`
&keepDefaults=`template,photo`
&contentFields=`{
«pagetitle»:«pagetitle»,
«published»:«published»,
«content»:«content»,
«template»:«template»,
«parent»:«parent»,

«photo»:«userpic»
}`
&prepare=`prepareUserContent`
&prepareProcess=`userPhoto`
&formControls=`published`
&emptyFormControls=`{
«published»:0
}`
&attachments=`userpic`
&fileRules=`{
«userpic»:{
«optional»:«Не удалось загрузить файл»
}
}`
&formTpl=`@CODE:
[+form.messages+]

div class=«form-group[+userpic.errorClass+]»>
label for=«userpic»>Картинки/label>
input class=«form-control» type=«file» name=«userpic»>
[+userpic.error+]
/div>


`
&messagesOuterTpl=`@CODE:[+messages+]`
&errorTpl=`@CODE:[+message+]`
&errorClass=` has-error`
&requiredClass=` has-warning`
&to=``
&subject=``
&reportTpl=`@CODE:
Пользователь [+user.fullname+] добавил [+form.date+] свое портфолио [+pagetitle+]
`
!]

Снип userPhoto либо не отрабатывает либо еще какая причина…
С несколькими файлами (для галереи) userPhoto в любом случае необходимо будет переписать — привести запись к формату multiTV и не только…

Если подскажете что не так с вызовом — радость случится, но что актуальнее это вывод в лог (&debug=`1`) работы userPhoto — понимаю что это банально просто, но $FormLister->debug->saveLog($files); (дописал в userPhoto) не срабатывает — не вызывается таки…
Комментарий отредактирован 2016-09-19 12:39:34 пользователем ghostery
avatar
Нужно отдельный компонент делать для такой задачи.
avatar
А поле userpic в prepare задается?
avatar
в prepeare только раздел задается — куда публиковать
avatar
Надо где-то задать поле userpic. В статье это происходит в сниппете userPhoto.
avatar
userpic задается в &contentFields=`{«photo»:«userpic»}` предположил, что этого достаточно…

Для последующего редактирования фото я планировал в prepeare добавить сниппет готовящий содержимое тв photo и images (tv type image и tv type multitv соответственно).
Простые tv type text я успешно добавил в форму, и они выводятся при редактировании — возможно необходимо добавить tv photo в &rules… попробую, но не уверен

Подскажите как вывести в лог DocLister'а prepeare и prepeareProcess?
Комментарий отредактирован 2016-09-19 13:37:22 пользователем ghostery
avatar
я вижу разделы для пред и пост кода — но там только массив с результатами — можно ли вывести туда к примеру echo $files;?
avatar
Наверное тут проблема возникает из-за того, что в форме есть файловое поле userpic, а нужно чтобы это поле отправилось в tv-параметр? Так?
avatar
Да, точно. При этом чтобы файл попал в указанную папку.
avatar
Тогда я не понимаю, для чего в статье писался раздел «Загрузка фото» ((: В сниппете, который вызывается в prepareProcess нужно всего-то выполнить:

$FormLister->setField('userpic','путь к файлу');


Объясняю совсем на пальцах: обычные поля из формы попадают в массив $_REQUEST (ну или $_POST/$_GET), FormLister сразу же их заносит в свой внутренний массив, из которого потом и берет значения для подстановки в шаблоны или записи куда-то. А информация о файлах попадает в совершенно другой массив $_FILES, сами файлы попадают во временную папку, и разбираться со всем этим должен разработчик. Потому что сниппет не может знать (кроме ситуации с прикреплением файлов к письму) зачем эти файлы вообще нужны, куда их сохранять под каким именем, куда потом вставлять эти имена.
avatar
все верно, файлы необходимо сохранить и передать пути к ним. Пытаюсь разобраться. В логе не нахожу раздела ктр автоматически логирует prepareProcess — просто prepare да
avatar
Они логируются по умолчанию. А так можно $FormLister->log('Заголовок', даннные);
avatar
В сниппете userPhoto оставил только это:
$files = $FormLister->getFormData('files');
$FormLister->log('Массив картинок?', $files);

страница редактирования статьи все чудесно сохраняет — так пишет.
В админке, отчеты — просмотр событий — в свежей записи FormLister\\Content все по порядку описано. В части Prepare to validate files массив [userpic] создается и ошибок валидации нет. Но дальше я предпологал увидеть запись ктр следует из снипа userPhoto, но потом File validation errors — пусто и Update record ну далее… Отсюда вывод что снип не вызывается (если код в нем корректен)…
Вот так выглядит вызов
[!FormLister?
&controller=`Content`
&formid=`article`
&clearCache=`1`
&submitLimit=`10`
&debug=`1`
&rules=`{
"pagetitle":{
	"required":"Обязательно введите заголовок",
	"minLength":{
		"params":10,
		"message":"Должно быть не менее 10 символов"
	}
},
"content":{
	"required":"Статья не может быть без текста",
	"minLength":{
		"params":100,
		"message":"Должно быть не менее 100 символов"
	}
},
"parent":{
	"in":{
		"message":"Выберите раздел"
	}
}
}`
&defaultsSources=`param:contentDefaults`
&contentDefaults=`{
"template":13,
"published":1
}`
&defaults=`photo`
&keepDefaults=`template,photo`
&contentFields=`{
"pagetitle":"pagetitle",
"published":"published",
"content":"content",
"template":"template",
"parent":"parent",
"photo":"userpic"
}`
&prepare=`prepareUserContent`
&prepareProcess=`userPhoto`
&formControls=`published`
&emptyFormControls=`{
"published":0
}`
&attachments=`userpic`
&fileRules=`{
"userpic":{
    "optional":"Не удалось загрузить файл"
}
}`
&formTpl=`@CODE:
<div class="row">
	<div class="col-md-10 col-md-offset-1">
		<div class="well">
			<form method="post"  enctype="multipart/form-data">
				[+form.messages+]
				<input type="hidden" name="formid" value="article">
				
				<div class="form-group[+pagetitle.errorClass+][+pagetitle.requiredClass+]">
					<label for="pagetitle">* Заголовок статьи</label>

						<input type="text" class="form-control" id="pagetitle" placeholder="Заголовок статьи" name="pagetitle" value="[+pagetitle.value+]">
						[+pagetitle.error+]

				</div>
				<div class="form-group[+parent.errorClass+]">
					<label for="department">Выберите раздел</label>
					<select name="parent" class="form-control">
					  [+parents+]
					</select>
					[+parent.error+]
				</div>
				
				<div class="form-group[+userpic.errorClass+]">
                    <label for="userpic">Картинка</label>
					<input class="form-control" type="file" name="userpic">
                    [+userpic.error+]
                </div>
				<div class="form-group[+content.errorClass+][+content.requiredClass+]">
					<label for="content">* Текст статьи</label>
						<textarea class="form-control" id="content" placeholder="Текст статьи" name="content" rows="10">[+content.value+]</textarea>
						[+content.error+]
				</div>
				<div class="checkbox[+published.requiredClass+]">
				  <label>
					  <input type="checkbox" name="published" value="1" [+c.published.1+]>
					Публиковать статью
				  </label>
					[+published.error+]
				</div>
				[+form.messages+]
				<div class="form-group">
						<button type="submit" class="btn btn-primary"><i class="glyphicon glyphicon-floppy-disk"></i> Сохранить</button>
				</div>
			</form>
		</div>
	</div>
</div>`
&messagesOuterTpl=`@CODE:<div class="alert alert-danger" role="alert">[+messages+]</div>`
&errorTpl=`@CODE:<span class="help-block">[+message+]</span>`
&errorClass=` has-error`
&requiredClass=` has-warning`
&to=`9667475@gmail.com`
&subject=`Новое портфолио на сайте`
&reportTpl=`@CODE:
<p>Пользователь [+user.fullname+] добавил [+form.date+] свое портфолио <a href="[+content.url+]">[+pagetitle+]</a></p>
`
!]

Возможно какие-то внутренние противоречия — &attachments=`userpic` все портит… или что нибудь банальнее
avatar
attachments нужен, чтобы указать, какие поля файловые. В общем я уже взял и проверил, все работает.
avatar
да, я вернул attachments сразу же)
подскажите — где моя ошибка? раз у вас все работает..(
avatar
Выложите код сниппета, может тогда прояснится что-то.
avatar
Использую код userPhoto из этого же топика
<?php
//получаем массив с загруженными файлами
$files = $FormLister->getFormData('files');
//проверяем, есть ли там нужный файл и загружен ли он успешно
if (isset($files['userpic']) && $files['userpic']['error'] === 0) {
    //задаем папку для хранения пользовательских фото   
    $dir = 'assets/images/userphoto/';
    //получаем имя загруженного файла, без расширения
    $filename = $FormLister->fs->takeFileName($files['userpic']['name']);
    //расширение отдельно
    $ext = $FormLister->fs->takeFileExt($files['userpic']['name']);
    //делаем транслитерацию имени файла, добавляем к нему расширение
    $filename = $modx->stripAlias($filename).'.'.$ext;
    //получаем предполагаемый путь к файлу, при необходимости переименовываем, чтобы не затереть файл с таким же именем
    $filename = $FormLister->fs->getInexistantFilename($dir.$filename,true);
    //пытаемся переместить файл в нужное место
    if ($FormLister->fs->makeDir($dir) && move_uploaded_file($files['userpic']['tmp_name'],$filename)) {
        //если получилось, то сохраняем в поле photo относительный путь к файлу
        $FormLister->setField('photo',$FormLister->fs->relativePath($filename));
    }
}
?>
avatar
<code>$FormLister->setField('photo',$FormLister->fs->relativePath($filename));</code>
Если в contentDefaults указано «photo(имя тв)»:«userpic(имя поля)», то и поле должно называться userpic, а не photo.
То есть или в сниппете поменять photo на userpic или в contentDefaults userpic на photo.
Комментарий отредактирован 2016-09-19 21:24:15 пользователем Pathologic
avatar
&contentDefaults=`{
"template":13,
"published":1
}`

не ставил
&contentFields=`{
...
"photo":"userpic",
...
}`


Я правильно понял —
$FormLister->setField('photo',$FormLister->fs->relativePath($filename));

эта строка записывает в тв с именем «photo»?
Попробую переименовать тв, чтоб не путаться еще больше
avatar
Эта строка не записывает tv, эта строка задает в массиве полей поле с именем photo. А в параметре contentFields указано, что нужно взять поле с именем userpic.
Комментарий отредактирован 2016-09-19 21:32:22 пользователем Pathologic
avatar
тогда я окончательно запутался…
Сниппет userPhoto загружает файл на сервер и записывает путь в тв photo.
Получается что он не работает и где-то есть банальная ошибка с моей стороны. Попробую завтра на свежую голову все перепроверить — ну и о результатах сообщу. Хочется иметь такое решение в обойме — вы формлистером классно позволили все вопросы лички, авторизации объединить. Еще раз спасибо вам!
avatar
Сниппет userPhoto загружает файл на сервер и записывает путь в тв photo.
Вот когда пройдет иллюзия, что сниппет userPhoto записывает что-то в тв, тогда все сразу же заработает.
avatar
В общем, создайте tv, пусть будет называться image. В форме создайте файловое поле userpic.
Сниппет userPhoto возьмите из топика, ничего не меняя.
В contentDefaults напишите «image»:«photo». Тогда будет работать.
Если понимание так и не приходит, то создайте сниппет userTest и добавьте в prepareProcess вместо userPhoto:

$FormLister->setField('testField','assets/images/logo.png');

В contentDefaults напишите «image»:«testField». Может так понятнее будет.
avatar
Спасибо! пробую
avatar
Тут вы любезно выложили тестовый сайт с разными вариациями формлистера. На нем я собственно и тренеруюсь…
Может быть необходимо переустановить формлистер или доклистер или модхAPI? — какие бы изменения не происходили в сниппете userPhoto ни в логе ни в доках ни в папке с файлами для картинок ничего не происходит. Чего там на зеркало пенять, как говорится…
Да, в логах добавляются названия тв для картинок, но они пусты… Сейчас проделал все что вы посоветовали — нолик..
[!FormLister?
&controller=`Content`
&formid=`article`
&clearCache=`1`
&submitLimit=`10`
&debug=`1`
&rules=`{
"pagetitle":{
	"required":"Обязательно введите заголовок",
	"minLength":{
		"params":10,
		"message":"Должно быть не менее 10 символов"
	}
},
"content":{
	"required":"Статья не может быть без текста",
	"minLength":{
		"params":100,
		"message":"Должно быть не менее 100 символов"
	}
},
"parent":{
	"in":{
		"message":"Выберите раздел"
	}
}
}`
&defaultsSources=`param:contentDefaults`
&contentDefaults=`{
"template":13,
"published":1,
"image":"photo"
}`
&keepDefaults=`template`
&contentFields=`{
"pagetitle":"pagetitle",
"published":"published",
"content":"content",
"template":"template",
"parent":"parent",
"image":"userpic"
}`
&prepareProcess=`userPhoto`
&prepare=`prepareUserContent`
&formControls=`published`
&emptyFormControls=`{
"published":0
}`
&attachments=`userpic`
&fileRules=`{
"userpic":{
    "optional":"Не удалось загрузить файл"
}
}`
&formTpl=`@CODE:
<div class="row">
	<div class="col-md-10 col-md-offset-1">
		<div class="well">
			<form method="post"  enctype="multipart/form-data">
				[+form.messages+]
				<input type="hidden" name="formid" value="article">
				
				<div class="form-group[+pagetitle.errorClass+][+pagetitle.requiredClass+]">
					<label for="pagetitle">* Заголовок статьи</label>

						<input type="text" class="form-control" id="pagetitle" placeholder="Заголовок статьи" name="pagetitle" value="[+pagetitle.value+]">
						[+pagetitle.error+]

				</div>
				<div class="form-group[+parent.errorClass+]">
					<label for="department">Выберите раздел</label>
					<select name="parent" class="form-control">
					  [+parents+]
					</select>
					[+parent.error+]
				</div>
				<div class="form-group[+userpic.errorClass+]">
                    <label for="userpic">Картинка - [+image.value+]</label>
					<input class="form-control" type="file" name="userpic" >
                    [+userpic.error+]
                </div>
				<div class="form-group[+content.errorClass+][+content.requiredClass+]">
					<label for="content">* Текст статьи</label>
						<textarea class="form-control" id="content" placeholder="Текст статьи" name="content" rows="10">[+content.value+]</textarea>
						[+content.error+]
				</div>
				<div class="checkbox[+published.requiredClass+]">
				  <label>
					  <input type="checkbox" name="published" value="1" [+c.published.1+]>
					Публиковать статью
				  </label>
					[+published.error+]
				</div>
				[+form.messages+]
				<div class="form-group">
						<button type="submit" class="btn btn-primary"><i class="glyphicon glyphicon-floppy-disk"></i> Сохранить</button>
				</div>
			</form>
		</div>
	</div>
</div>`
&messagesOuterTpl=`@CODE:<div class="alert alert-danger" role="alert">[+messages+]</div>`
&errorTpl=`@CODE:<span class="help-block">[+message+]</span>`
&errorClass=` has-error`
&requiredClass=` has-warning`
&to=`9667475@gmail.com`
&subject=`Новое портфолио на сайте`
&reportTpl=`@CODE:
<p>Пользователь [+user.fullname+] добавил [+form.date+] свое портфолио <a href="[+content.url+]">[+pagetitle+]</a></p>
`
!]


Код сниппета userPhoto не меняя взял из топика

<?php
//получаем массив с загруженными файлами
$files = $FormLister->getFormData('files');
//проверяем, есть ли там нужный файл и загружен ли он успешно
if (isset($files['userpic']) && $files['userpic']['error'] === 0) {
    //задаем папку для хранения пользовательских фото   
    $dir = 'assets/images/userphoto/';
    //получаем имя загруженного файла, без расширения
    $filename = $FormLister->fs->takeFileName($files['userpic']['name']);
    //расширение отдельно
    $ext = $FormLister->fs->takeFileExt($files['userpic']['name']);
    //делаем транслитерацию имени файла, добавляем к нему расширение
    $filename = $modx->stripAlias($filename).'.'.$ext;
    //получаем предполагаемый путь к файлу, при необходимости переименовываем, чтобы не затереть файл с таким же именем
    $filename = $FormLister->fs->getInexistantFilename($dir.$filename,true);
    //пытаемся переместить файл в нужное место
    if ($FormLister->fs->makeDir($dir) && move_uploaded_file($files['userpic']['tmp_name'],$filename)) {
        //если получилось, то сохраняем в поле photo относительный путь к файлу
        $FormLister->setField('photo',$FormLister->fs->relativePath($filename));
    }
}
?>


в коменте к его финальной строке записано — //если получилось, то сохраняем в поле photo относительный путь к файлу
т.о. делается защита от манипуляций на стороне пользователя.

Наверное мне на свалку пора)

Что меня смущает, так это то, что мои попытки вывести в лог произвольную строку ничем не закончились
avatar
&defaults=`{"photo":""}`
&keepDefaults=`template,photo`

вот это еще в вызове тут забыл добавить
avatar
И осталось только на само название photo подозрение — в данных пользователя есть одноименное (а снип все-же писался для подгрузки картинки пользователя — при регистрации)
avatar
Может быть необходимо переустановить формлистер или доклистер или модхAPI?

Необходимо удалить и формлистер, и доклистер, и апи. Тогда все хорошо будет, раньше ведь как-то делали сайты без всей этой непонятной хрени.
avatar
Вы правы. Разобраться не получилось у меня — сделаю по старинке.
<code>require_once('assets/libs/document.class.inc.php')$doc = new Document();;
</code>
запишу стандартные поля, потом тв, потом картинки

$images_arr = [];
$uploadpath = 'assets/images/userphoto';
    $target_dir = $uploadpath;
	 foreach($_FILES['files']['name'] as $key=>$val){       
        $target_file = $target_dir.$_FILES['files']['name'][$key];
        if(move_uploaded_file($_FILES['files']['tmp_name'][$key],$target_file)){
            $images_arr[] = $target_file;
        }
    }

потом приведу список к необходимому формату и запишу все в бд…
потом сделаю нечто подобное для редактирования и будет работать

Хотел чтобы подобных вопросов при использовании FormLister ни у кого не возникало — спасибо что попытались помочь, но пока в моих знаниях такие дыры, то ни на что кроме попрошайничества эти вопросы и не похожи)
Комментарий отредактирован 2016-09-21 12:41:02 пользователем ghostery
avatar
Если у Вас каталог статей, то посмотрите на это
modx.im/blog/addons/2883.html
и там мой последний комментарий
avatar
это меня еще больше запутало)
Как я понимаю процесс: По окончании валидации полей и файлов (два поля — с одним и несколькими картинками) в userPhoto должны попасть значения соответствующих полей. $files = $FormLister->getFormData('files');
проверю наличие файлов, задам путь и т.д. — все по аналогии со сниппетом userPhoto из топика…
Но в логе пусто, даже методом тыка не выходит увидеть что не так
avatar
Разделите на 2 задачи:
1. Личный кабинет. Его можно делать на чем угодно — можно на webloginpe, можно на FormLister. Это регистрация и вход на сайт.
2. Публикация статей зарегистрированным пользователем. Это уже другая задача.
Вот для этой задачи №2 я и посоветовал применить компонент Easy Board — это каталог (не важно чего ), он позволяет веб-пользователю выбрать нужную категорию и подкатегорию, опубликовать статью и прикрепить к ней фото. Есть и другие поля, их можно в вашем случае не использовать. При желании текст статьи можно поменять, можно поменять и фото к статье. Статью можно удалить (снять с публикации). Статей у каждого веб-пользователя может быть много в любых категориях сайта.
avatar
Спасибо!
Личка через формлистер сделана и задача только в добавлении — сохранении — замене — удалении файлов… И есть уже код альтернативного способа — минуя всю изящность формлистера, но не хотелось бы ее портить топороом.
Ну и для общества такой пример был бы полезен
avatar
Я сам в такой задаче делал работу с файлами мимо FormLister, вообще не нравится мне загружать файлы через формы. Внешне выглядит как на modx.pro, да и клиентскую часть я в какой-то мере позаимствовал из Tickets.
avatar
По окончании валидации полей и файлов запускаются сниппеты из prepareProcess. В них можно задать какие-то дополнительные поля через $FormLister->setField. Потом выполнится метод process, где согласно параметру contentFields, будет сделана запись в таблицу.
В логе после записи File validation errors должна быть запись Prepare finished и там массив полей, где и должно быть видно поле userpic.
Так что из идей могу только предположить, что сниппет из prepareProcess не выполняется по каким-то причинам, например там ошибка в коде. Можно сделать в конце сниппета die('ok') и будет видно, работает он или нет.
avatar
die('ok') поставил, но после File validation errors идет Update record и в нем есть пустое поле photo куда и должно попадать значение пути к файлу, а в Prepare finished ничего о картинках нет. Это ведь процессы до валидации формы — сразу отрабатывается снип для подготовки разделов (ваш, из тестового сайта для формлистера)
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.