I created a form with validation rules. Everything is fine, form is visible and works.
Problem is with validators. Only first validator works in addValidators([ ....])
My form class source code:
public function initialize()
{
$title = new Text('title');
$title->setLabel('Title of article');
$title->setFilters([
'striptags', 'trim'
]);
$title->addValidators([
new PresenceOf([
'message' => 'Title can not be empty'
]),
new StringLength([
'min' => 5,
'messageMinimum' => 'Title is too short. Should has more than 5 letters'
]),
new MYArticleAddCheckTitleValidator([
'message' => 'aaaaaaaaaaaaaaa'
])
]);
$this->add($title);
..........
Validator PresenceOf works fine. validation flash message is visible.
Validator StringLength does not work. It looks like form doesn't know about it
Validator MYArticleAddCheckTitleValidator (my own validator class) - the same as StringLength.
Phalcon version 2.0.4 on windows.
Any proposition, or suggestions ?
Thanks a lot.
Using Phalcon\Flash\Direct
The best way to get around this problem is to use flash messages, with the class Phalcon\Flash\Direct. You can find the documentation here.
This strategy is used by the phalcon vokuro project which I suggest you to look at.
The key of this solution is the message() function inside your form class.
Form class
<?php
namespace Your\App\Forms;
use Phalcon\Forms\Form;
use Phalcon\Forms\Element\Text;
use Phalcon\Validation\Message;
use Phalcon\Validation\Validator\PresenceOf;
use Phalcon\Validation\Validator\StringLength;
use Your\App\Validation\MYArticleAddCheckTitleValidator;
class YourForm extends Form {
public function initialize()
{
$title = new Text('title');
$title->setLabel('Title of article');
$title->setFilters([
'striptags', 'trim'
]);
$title->addValidators([
new PresenceOf([
'message' => 'Title can not be empty'
]),
new StringLength([
'min' => 5,
'messageMinimum' => 'Title is too short. Should has more than 5 letters'
]),
new MYArticleAddCheckTitleValidator([
'message' => 'aaaaaaaaaaaaaaa'
])
]);
$this->add($title);
}
/**
* Prints messages for a specific element. Call it in the view
*/
public function messages($name)
{
if ($this->hasMessagesFor($name)) {
foreach ($this->getMessagesFor($name) as $message) {
$this->flash->error($message);
}
}
}
}
Controller
<?php
namespace Your\App;
use Your\App\YourForm;
class YourController extends ControllerBase
{
public function indexAction()
{
$form = new YourForm();
$this->view->form = $form;
if($this->request->hasQuery('title')){
if ($form->isValid($this->request->getQuery()) != false) {
// Code when form is valid
}
}
}
}
View
If you follow the suggested schema should be located in /app/views/your/index.html
<form method="GET" action="">
<?= $form->label('title') ?>
<?= $form->render('title')?>
<?= $form->messages('title') //show messages here ?>
</form>
If you have more than one form, it is useful to register the flash service with the DI.
When you define your services (could be in the index.php in the root folder or services.php in the /app/config/ folder) you define your flash service:
<?php
use Phalcon\DI\FactoryDefault;
$di = new FactoryDefault();
// Register the flash service with custom CSS classes
$di->set('flash', function () {
$flash = new Phalcon\Flash\Direct(
array(
'error' => 'your-error-class',
'success' => 'your-success-class',
'notice' => 'your-notice-class',
'warning' => 'your-warning-class'
)
);
return $flash;
});
Related
I have a Static Page in OctoberCMS named General that has a bunch of site-wide settings including phone number and address. Is it possible to access this page in code to read these settings from its ViewBag?
UPDATE: a plugin was created with the following, where properties like twitter_username for example can now be accessed in templates with {{ general('twitter_username') }}:
use System\Classes\PluginBase;
use RainLab\Pages\Classes\Page;
use Cms\Classes\Theme;
class Plugin extends PluginBase
{
private static $generalViewBag = null;
public function registerMarkupTags()
{
return [
'functions' => [
'general' => function($var) {
if (self::$generalViewBag === null) {
self::$generalViewBag = Page::load(Theme::getActiveTheme(), 'general')
->getViewBag();
}
return self::$generalViewBag->$var;
},
],
];
}
}
The twitter_username form field was added to the General page in the backend using a separate plugin:
use System\Classes\PluginBase;
use Event;
class Plugin extends PluginBase
{
public function boot()
{
Event::listen('backend.form.extendFields', function($widget) {
if (! $widget->getController() instanceof \RainLab\Pages\Controllers\Index) {
return;
}
if (! $widget->model instanceof \RainLab\Pages\Classes\Page) {
return;
}
switch ($widget->model->fileName) {
case 'general.htm':
$widget->addFields([
'viewBag[twitter_username]' => [
'label' => 'Twitter username',
'type' => 'text',
'tab' => 'Social Media',
],
], 'primary');
break;
}
});
}
}
yes you can do it actually you need to use this code in page life-cycle method
In page code block you can use something like this OR anywhere else
use RainLab\Pages\Classes\Page as StaticPage;
function onStart() {
$pageName = 'static-test';
$staticPage = StaticPage::load($this->controller->getTheme(), $pageName);
dd($staticPage->viewBag);
}
let me know if it you find any issues
I'm reviewing a basic contact form which is not associated with any model. I would like some advice on the best way to leverage Cake's automatic view rendering of field errors for this situation.
Controller
Performs validation through a custom Validator.
public function index()
{
if ($this->request->is('post')) {
// Validate the form
$validator = new EnquiryValidator();
$data = $this->request->data();
$errors = $validator->errors($data);
if (empty($errors)) {
// Send email, etc.
// ...
// Refresh page on success
}
// Show error
$this->Flash->error('Unable to send email');
}
}
View
<?= $this->Form->create(); ?>
<?= $this->Form->input('name', [
'autofocus' => 'autofocus',
'placeholder' => 'Your name',
'required'
]);
?>
<?= $this->Form->input('email', [
'placeholder' => 'Your email address',
'required'
]);
?>
<?= $this->Form->input('subject', [
'placeholder' => 'What would you like to discuss?',
'required'
]);
?>
<?= $this->Form->input('message', [
'label' => 'Query',
'placeholder' => 'How can we help?',
'cols' => '30',
'rows' => '10',
'required'
]);
?>
<div class="text-right">
<?= $this->Form->button('Send'); ?>
</div>
<?= $this->Form->end(); ?>
Currently the form will not show any errors next to the input fields. I assume it's because there is no entity associated with the form or something like that, but I'm not sure.
What is the best solution? Can the validation be performed in a better way to automatically provide field errors in the view?
Modelless forms
Use a modelless form. It can be used to validate data and perform actions, similar to tables and entities, and the form helper supports it just like entities, ie, you simply pass the modelless form instance to the FormHelper::create() call.
Here's the example from the docs, modified a little to match your case:
src/Form/EnquiryForm.php
namespace App\Form;
use App\...\EnquiryValidator;
use Cake\Form\Form;
use Cake\Form\Schema;
use Cake\Validation\Validator;
class EnquiryForm extends Form
{
protected function _buildSchema(Schema $schema)
{
return $schema
->addField('name', 'string')
->addField('email', ['type' => 'string'])
->addField('subject', ['type' => 'string'])
->addField('message', ['type' => 'text']);
}
protected function _buildValidator(Validator $validator)
{
return new EnquiryValidator();
}
protected function _execute(array $data)
{
// Send email, etc.
return true;
}
}
in your controller
use App\Form\EnquiryForm;
// ...
public function index()
{
$enquiry = new EnquiryForm();
if ($this->request->is('post')) {
if ($enquiry->execute($this->request->data)) {
$this->Flash->success('Everything is fine.');
// ...
} else {
$this->Flash->error('Unable to send email.');
}
}
$this->set('enquiry', $enquiry);
}
in your view template
<?= $this->Form->create($enquiry); ?>
See also
Cookbook > Modelless Forms
In Symfony 2.8 I've got Movie entity with actors field, which is ArrayCollection of entity Actor (ManyToMany) and I wanted the field to be ajax-loaded Select2.
When I don't use Ajax, the form is:
->add('actors', EntityType::class, array(
'class' => Actor::class,
'label' => "Actors of the work",
'multiple' => true,
'attr' => array(
'class' => "select2-select",
),
))
It works, and this is what profiler displays after form submit: http://i.imgur.com/54iXbZy.png
Actors' amount grown up and I wanted to load them with Ajax autocompleter on Select2. I changed form to ChoiceType:
->add('actors', ChoiceType::class, array(
'multiple' => true,
'attr' => array(
'class' => "select2-ajax",
'data-entity' => "actor",
),
))
//...
$builder->get('actors')
->addModelTransformer(new ActorToNumberModelTransformer($this->manager));
I made DataTransformer:
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Persistence\ObjectManager;
use CompanyName\Common\CommonBundle\Entity\Actor;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
class ActorToNumberModelTransformer implements DataTransformerInterface
{
private $manager;
public function __construct(ObjectManager $objectManager)
{
$this->manager = $objectManager;
}
public function transform($actors)
{
if(null === $actors)
return array();
$actorIds = array();
$actorsArray = $actors->toArray();
foreach($actorsArray as $actor)
$actorIds[] = $actor->getId();
return $actorIds;
}
public function reverseTransform($actorIds)
{
if($actorIds === null)
return new ArrayCollection();
$actors = new ArrayCollection();
$actorIdArray = $actorIds->toArray();
foreach($actorIdArray as $actorId)
{
$actor = $this->manager->getRepository('CommonBundle:Actor')->find($actorId);
if(null === $actor)
throw new TransformationFailedException(sprintf('An actor with id "%s" does not exist!', $actorId));
$actors->add($actor);
}
return $actors;
}
}
And registered form:
common.form.type.movie:
class: CompanyName\Common\CommonBundle\Form\Type\MovieType
arguments: ["#doctrine.orm.entity_manager"]
tags:
- { name: form.type }
But seems like the reverseTransform() is never called. I even put die() at the beginning of it - nothing happened. This is, what profiler displays after form submit: http://i.imgur.com/qkjLLot.png
I tried to add also ViewTransformer (code here: pastebin -> 52LizvhF - I don't want to paste more and I can't post more than 2 links), with the same result, except that reverseTransform() is being called and returns what it should return.
I know that this is an old question, but I was having a very similar problem. It turned out that I had to explicitly set the compound option to false.
That is to say, for the third parameter to the add() method, you need to add 'compound => false'.
How do you validate the new Google reCaptcha using volt and phalcon techniques?
(Just wanted to share what I did to make it work, see answer below, hope it helps...)
What you will need
A Validator (RecaptchaValidator in this case)
Form implementation
Controller implementation (actually no changes needed here, but for completeness...)
View implementation
(optional) Config entries for your recaptcha keys and url (nicer/cleaner this way)
(optional) A recaptcha element for automatic rendering (if you prefer the render method)
The Validator
The validator is the most crucial part of this, all other things are rather intuitive ...
use \Phalcon\Validation\Validator;
use \Phalcon\Validation\ValidatorInterface;
use \Phalcon\Validation\Message;
class RecaptchaValidator extends Validator implements ValidatorInterface
{
public function validate(\Phalcon\Validation $validation, $attribute)
{
if (!$this->isValid($validation)) {
$message = $this->getOption('message');
if ($message) { // Add the custom message defined in the "Form" class
$validation->appendMessage(new Message($message, $attribute, 'Recaptcha'));
}
return false;
}
return true;
}
/********************************************
* isValid - Return Values
* =======================
* true .... Ok
* false ... Not Ok
* null .... Error
*/
public function isValid($validation)
{
try {
$config = $validation->config->recaptcha; // not needed if you don't use a config
$value = $validation->getValue('g-recaptcha-response');
$ip = $validation->request->getClientAddress();
$url = $config->verifyUrl; // or 'https://www.google.com/recaptcha/api/siteverify'; without config
$data = ['secret' => $config->secretKey, // or your secret key directly without using the config
'response' => $value,
'remoteip' => $ip,
];
// Prepare POST request
$options = [
'http' => [
'header' => "Content-type: application/x-www-form-urlencoded\r\n",
'method' => 'POST',
'content' => http_build_query($data),
],
];
// Make POST request and evaluate the response
$context = stream_context_create($options);
$result = file_get_contents($url, false, $context);
return json_decode($result)->success;
}
catch (Exception $e) {
return null;
}
}
}
Form (class) implementation
class SignupForm extends Form
{
public function initialize($entity = null, $options = null)
{
// Name (just as an example of other form fields)
$name = new Text('name');
$name->setLabel('Username');
$name->addValidators(array(
new PresenceOf(array(
'message' => 'Please enter your name'
))
));
$this->add($name);
// Google Recaptcha v2
$recaptcha = new Check('recaptcha');
$recaptcha->addValidator(new RecaptchaValidator([
'message' => 'Please confirm that you are human'
]));
$this->add($recaptcha);
// Other form fields...
}
Controller implementation
Even though the controller is the same as with every other form, for completeness sake here an example...
class SessionController extends \Phalcon\Mvc\Controller
{
public function signupAction()
{
$form = new SignupForm();
if ($this->request->isPost()) {
if ($form->isValid($this->request->getPost()) != false)
{
// Add user to database, do other checks, etc.
// ...
}
}
$this->view->form = $form;
}
}
View implementation
For the view you can either just put the html there or let it be rendered by the engine. If you want it rendered instead (with e.g. {{ form.render('recaptcha') }}), you will have to also create an Recaptcha Element instead of using one of the defaults (see last point in this answer for that).
...
{{ form('class':'signupForm') }}
<fieldset>
<div>{{ form.label('name') }}</div>
{{ form.render('name') }}
{{ form.messages('name') }}
<!-- other elements here -->
<!-- ... -->
<div class="g-recaptcha" data-sitekey="{{ this.config.recaptcha.publicKey }}"></div>
{{ form.messages('recaptcha') }}
If you don't wanna use a config for your public key (next section), just set the value of the data-sitekey to your personal (Google reCaptcha) public key.
Also don't forget to include the script (<script src='https://www.google.com/recaptcha/api.js'></script>) somewhere (e.g. in your html head section).
(Optional) Config
If you wanna use the config to store your recaptcha keys, also add the following to your config/config.php ...
// config/config.php
return new \Phalcon\Config([
'application' => [
'controllersDir' => __DIR__ . '/../../app/controllers/',
'modelsDir' => __DIR__ . '/../../app/models/',
'formsDir' => __DIR__ . '/../../app/forms/',
'viewsDir' => __DIR__ . '/../../app/views/',
'pluginsDir' => __DIR__ . '/../../app/plugins/',
'libraryDir' => __DIR__ . '/../../app/library/',
'cacheDir' => __DIR__ . '/../../app/cache/',
'baseUri' => '/',
],
// other configs here
// ...
'recaptcha' => [
'publicKey' => 'your public key',
'secretKey' => 'your private key',
'verifyUrl' => 'https://www.google.com/recaptcha/api/siteverify',
],
]);
To be able to access the config in your view, you might also need to add $di->set('config', $config); to your dependency injector (typically within config/services.php).
(Optional) Recaptcha Element
If you want your recaptcha to be rendered for you (instead of you putting the div directly in the view), you will need a separate \Phalcon\Forms\Element\ ...
class Recaptcha extends \Phalcon\Forms\Element
{
public function render($attributes = null) {
return '<div class="g-recaptcha" data-sitekey="'
.$this->config->recaptcha->publicKey
.'"></div>';
}
}
You will also have to change your Form accordingly:
// ...
$recaptcha = new Recaptcha('recaptcha');
$recaptcha->addValidator(new RecaptchaValidator([
'message' => '...'
]));
// ...
And finally also your View:
{{ form.render('recaptcha') }}
In the application login I have the following code that throw ...HttpException on logging errors:
// common/models/LoginForm.php which is called from the backend SiteController actionLogin method as $model = new LoginForm();
public function loginAdmin()
{
//die($this->getUser()->getRoleValue()."hhh");
if ($this->getUser()->getRoleValue() >= ValueHelpers::getRoleValue('Admin') && $this->getUser()->getStatusValue() == ValueHelpers::getStatusValue('Active')){
if ($this->validate()){
return \Yii::$app->user->login($this->getUser(), $this->rememberMe ? 3600*24*30:0);
}
else{
throw new \yii\web\NotFoundHttpException('Incorrect Password or Username.');
}
}
else{
throw new \yii\web\ForbiddenHttpException('Insufficient privileges to access this area.');
}
}
It is working fine, but I want to customize the page the rendered with each of NotFoundHttpException and ForbiddenHttpException. I tried to search the Yii2 api to find any parameters that may define view in the construct of the object but I could not find that. So, is there any way to custom the view of the exception?
From Mihai P. (Thank you) answer, I have got this answer. I opened the file of the error class at vendor\yiisoft\yii2\web\ErrorAction.php and I have found that it has a public property for view, so, I decided to use it and hence I defined it in the error array of the actions method as follows:
public function actions()
{
return [
'error' => [
'class' => 'yii\web\ErrorAction',
'view' => '#common/views/error.php',
],
];
}
Finally, in common folder I had to create a new folder named views and fill it with a view file called error.php with the following simple code
<?php
$this->title = $name;
echo $name;
echo "<br>";
echo $message;
echo "<br>";
echo $exception;
The three variables in the view $name, $message and $exception are supplied from the ErrorAction object and they could be found in the last lines of that file
...
else {
return $this->controller->render($this->view ?: $this->id, [
'name' => $name,
'message' => $message,
'exception' => $exception,
]);
}
...
If you take a look here https://github.com/yiisoft/yii2-app-advanced/blob/master/frontend/controllers/SiteController.php
You can see that it uses an external action to handle the errors
/**
* #inheritdoc
*/
public function actions()
{
return [
'error' => [
'class' => 'yii\web\ErrorAction',
],
'captcha' => [
'class' => 'yii\captcha\CaptchaAction',
'fixedVerifyCode' => YII_ENV_TEST ? 'testme' : null,
],
];
}
You can either create your own ErrorAction file that extends the default one and use yours instead of the default one from Yii, or just comment out that action and create a normal actionError and put it in there.