Is there any way to set a layout to my messages in my model code?
Here is my model:
var $validate = array(
'email' => array(
'rule' => array('email', true),
'message' => 'Please supply a valid email address.'
)
);
This can be done with CakePHP. However, you have your concerns mixed up. The Model is there for data gathering, manipulation and massaging. The layout of the data is under the responsibilities of the View. In CakePHP specifically the Form Helper.
For more info about how to specify your own layout for a data validation message check out:
http://book.cakephp.org/view/1639/options-inputDefaults
They provide a pretty great code sample on exactly how to do this.
I also highly suggest you read through the whole book. It will prove invaluable.
Edit: Answer after clarification from comment
You would create an element and put it in app/views/elements. Should name the file using normal Cake conventions. Let's go with flash_error.
You would set this up to be your HTML that you want displayed. To make sure your message is displayed simply add this bit of PHP wherever is appropriate
<?php echo $message; ?>
That's step 1.
Step 2 is in your $this->setFlash() call pass the appropriate parameters. So your new calls would look like this with the element we named above:
$this->setFlash($message, 'flash_error');
Now your setFlash messages will use the layout defined in step 1. Wanna different layout? Just create a new element and pass the new element name.
The setFlash() method has 2 more parameters that come in handy (particularly if you want to have multiple flash() messages on the same page). Another link to the book:
http://book.cakephp.org/view/1313/setFlash
Related
Our site is multilanguage (for a client). But instead of translate words like "login" in the PO files, we need to extract them from our translations table in the database. This is a client requirement.
I did research in which way I could make a function that is accessible from anywhere (view, controller, etc.). I concluded that the best way was to make a custom Library, because it said it could be used anywhere.
But I can't use it in my model, it gives me the error that I can't use ClientnameLibrary::translate('login') on that place. I added the line App::uses('ClientnameLibrary', 'Lib'); in my model, and tried this in the validate part:
'lastname' => array(
'notEmpty' => array(
'rule' => array('notEmpty'),
'message' => ClientnameLibrary::translate('lastname_validation_error'),
),
),
Is there a way I can use my custom Library in a model? And if not, what is the best way to translate the validation messages in my model (from the database)?
This is the code I use in controllers/views:
App::uses('ClientnameLibrary', 'Lib'); //include at top of file
echo ClientnameLibrary::translate('login'); //to get translated word from database
Save the DB translations in a po file when the translations change. Without caching, reading every translation with a new query is silly, so you will have to cache it any way. So just turn the DB content into a po file and use the standard translation functions from CakePHP?
Check how the i18n shell extracts the translation strings into pot (NOT po) files. You could probably override the shell to update your DB with the required identifiers as well.
I'm new to CakePHP, and am trying to do some blog-like exercise, then I ran into some problems.
See have a Model called Post, then under the PostsController I generated a view action for checking a single blog post. What I want is to allow the users to be able to add comment to the post in the Posts/view page instead of being redirected to a new Comments/add page. To do that I need to tell my CommentsController which post the user is commenting on. So wrote this in my /app/View/Posts/view.ctp:
<?php
echo $this->Form->create('Comment', array('controller' => 'comments', 'action' => 'add');
echo $this->Form->input('content', array('row' => '3'));
// this is the line I'm not sure about
echo $this->Form->input('post_id', array('default' => $post['Post']['id'], 'type' => 'hidden'));
echo $this->Form->end('Submit');
?>
Now this solution will send the value of $post['Post']['id'] to the add action in CommentsController in the form of $this->request->data['post_id'], but call me anal, I worry that whether this is the correct, or "professional" way to do this, since one can easily make the hidden field visible by altering some attributes with "inspect element' inside of any modern browser, leaving not necessarily potential security vulnerabilities, but something I personally don't feel comfortable with. So please, if anyone's worked with CakePHP before, share some experience with me on that.
First you can shorten that line to:
$this->Form->hidden('post_id', array('value' => $post['Post']['id']));
To prevent form tampering use the security component. It will:
Restricting which HTTP methods your application accepts.
CSRF protection.
Form tampering protection
Requiring that SSL be used.
Limiting cross controller communication.
Also I would validate that any data that is processed is valid. So you might want to check that the post exists and is public as well for example to prevent people can add comments to a non-public post. The same concept applies for everything: Never trust any input, not user nor API. Always validate the data and circumstances.
I am using the Security component in my AppController. I need to build a check form input that will allow custom formatting of each item in the list. To accomplish this, I am processing each item in the $options set using a foreach and creating the new element like so:
foreach ($fileTypes as $fileType_key => $fileType_value) {
echo $this->Form->input(
'FilesIncluded.' . $fileType_key,
array(
'type' => 'checkbox',
'value' => $fileType_key,
'label' => false,
'div' => false,
'before' => '<span class="checkbox clearfix"><span class="check">',
'after' => '</span><label for="add-product-check-sub-cat">' . $fileType_value . '</label></span>',
'hiddenField' => false,
)
);
}
There is a few things to note:
I am setting each check box to data[FilesIncluded][{UUID}] (where UUID actually represents the UUID pf the FilesIncluded) instead of data[FilesIncluded][]
FilesIncluded is not part of the form model, so it will appear in $this->request->data as $this->request->data['FilesIncluded'] instead of $this->request->data['Model']['column']
What I am trying to figure out is why this throws an auth security risk? When I change the field name from 'FilesIncluded.' . $fileType_key to something with a counter in it like 'FilesIncluded.' . $count . '.id', it seems to work without throwing any security auth errors. Any ideas how to make this work the way I am expecting it to?
UPDATE:
The other issue is being able to maintain a fixed set of FileTypes. For example, I want to be able to control the HABTM records that can be selected from the checkbox. For example, I will display this list:
http://cl.ly/image/0b1Q3C0d0w1Y
And only when the user selects the records will they be stored as hasMany. Then when it comes time to edit, I want to not only be able to show the same set of records, but then have them associated to the records the user saved.
(Probable) Cause
You're probably getting the Security error, because you're disabling the hiddenField for the checkboxes. The Security components checks if a Submitted Form is valid (i.e. not tampered with) by calculating a checksum, based on the names of the Form fields, when creating the form and comparing this with the data received when submitting the form.
By suppressing the hiddenField, the checkbox will not be present in the posted data if it is not checked (Non-checked checkboxes are never sent in HTML forms). As explained above, CakePHP calculates a checksum based on the fields/inputs it expects and the actual fields (data) it receives. By disabling the hiddenField, the checksum calculated from the posted data will depend on wether a checkbox was checked or not, which will invalidate the posted data
Workarounds
There may be some Workarounds;
Do not suppress the 'hiddenField' This will make sure that the checkboxes will always be present in the posted data. If a checkbox is not checked, the value of the checkbox will be 0 (zero). If the checkbox is checked, its posted value will be the specified value of the checkbox (or 1 if no value is specified)
Exclude your custom inputs from the checksum. You can exclude fields from the checksum via $this->Form->unlockField('fieldname');. CakePHP will ignore those inputs when calculating the Security checksum.
Documentation: FormHelper::unlockField()
Notes
Although these Workarounds may help, I'd suggest to not re-invent the wheel. By changing the names of your inputs, you're no longer following the CakePHP conventions. Sticking to the conventions often saves you a lot of time.
For example, saving related data can be performed with a single Model::saveAssociated() call. If your Model-relations are properly set, for example:
Document->hasMany->UserFiles, then CakePHP will insert/update both the Document and UserFiles data automatically.
Read the documentation here Saving Related Model Data (hasOne, hasMany, belongsTo)
In cakephp [2.2] I have baked everything and my "people" view is quite busy with relations and phones and addresses and other related data. I do want all of that information visible in the people view, though not quite in the baked layout.
How should I handle those portions of the related data? I'm not sure if I should use elements or extended views or plugins or what, I'm kinda new to this and the documentation wasn't clear to me (at my level) which should be used when. The baked code seemed to be a monolithic approach, so I didn't get much help looking there.
Once the user chooses to edit a phone number (for instance) from the listing on the person view, it takes them to the phone edit view and then returns them to the phone listing (index view) and not the person view that they were on. How do I get them back to the person view instead?
The blog example they provide is nice, but is there a "reference" application somewhere for cakephp that demonstrates best practices on a wide variety of their features? I couldn't find one, or anything more than just a simple app example.
Thanks, I appreciate the guidance.
This is a rather broad question, but I'm going to try and answer it. I'm not sure how advanced you're programming knowledge is, so forgive me if I'm rehashing things you already know. First, this article was a great help when I started to use the framework for the first time as it explains what code should go where and why. It's the closest I've seen to a "reference application", which would actually be a great learning tool. You could try and have a look at some of the higher profile Cake applications, like Croogo (a Cake-based CMS). But the codebase is bound to be a little bit complex.
Personally I would use elements when you want to actually reuse them in different views. The problem however, is feeding the element its data. There's a method called requestAction, but even the manual states that this should be used with moderation and in combination with caching. The problem is that using a lot of requestAction calls in different elements litters your Controllers with methods and doesn't adhere to the "Skinny Controllers, Fat Models" mantra.
I would put most of the related data calls in their respective Models and call those Model methods from the Controller and feed them to the View. So let's say you want the 10 latest PhoneNumbers and related Users.
You would have a method in your PhoneNumber model which returns an array of users and their phonenumbers. Use the Containable behaviour to limit the number of related models which are returned. The code below is an example, so the practical implementation might vary:
public function getRecentPhoneNumbers($limit=10) {
$phoneNumbers = array();
$phoneNumbers = $this->find('all', array(
'limit' => $limit,
'contain' => array('User'),
'order' => 'PhoneNumber.id DESC'
));
return $phoneNumbers;
}
If the PhoneNumber and User model are properly related you would be able to call getRecentPhoneNumbers() from the User model:
$this->PhoneNumber->getRecentPhoneNumbers(10)
Or from the Users Controller:
$this->User->PhoneNumber->getRecentPhoneNumbers(10)
Say you have an element which shows a list of those 10 numbers and it accepts a variable called $recentPhonenumbers, you then set the variable in the relevant UsersController method with the returned array from the getRecentPhoneNumbers call:
$this->set('recentPhonenumbers', $this->User->PhoneNumber->getRecentPhoneNumbers(10));
This will make it available to the View that contains the element.
The extended views are relatively new (from Cake 2.1 and onwards) and I haven't used them, but seem a great way to create conditional markup.
As for the second question, redirecting the user to the person view, rather than the index view. This is a matter of adjusting the redirect (see the manual for more details) in the edit() method of the Controller. Standard baked edit() methods accept an $id parameter you can use this to redirect to the view() (which probably also accepts an $id paramater).
So the redirect probably looks something like this:
$this->redirect(array('controller' => 'users', 'action' => 'index'));
Change it to:
$this->redirect(array('controller' => 'users', 'action' => 'view', $id));
I need to perform some validation. I don't have the model in the application.
Does anyone know how to do the validation without a model?
Can you show me using a small sample or statement?
Honestly, I'd create a model just for the validation. You can create a model that doesn't use a table by adding
var $useTable = false;
And then create a validation array with rules for each field you want to validate:
var $validate = array('login' => 'alphaNumeric','email' => 'email','born' => 'date');
Then, in your controller, do something like:
$this->MyModel->set($this->data);
if($this->MyModel->validates()){
// do stuff with valid data
}
If you really, really can't use a model, then you'll have to simply loop over each value in $this->data in your controller action and validate it against a regular expression or use the Validation::[rule]() stuff, like:
if(Validation::email($someThingThatMightBeAnEmailAddress)){
// do stuff with valid email address.
}
You can perform validation of form data in CakePHP without having to create a model.php file. There are many times when I need to do this, and storing model.php files that do nothing more then validation is a poor usage of the model design pattern.
Another problem with CakePHP is that sometimes validation rules are common across multiple models. It would be nice to move validation out of the model, much in the way behaviors are to their own subfolder. That way we can re-use them or use them without a model.
Another problem with validation is that it's dependent upon the model alias. If you have a model called "Email" without a table to perform validation, then the posted form must also use "Email". If the form uses a alias different from the controller, then you have to set the action. A lot of extra steps just to do validation. You can't re-use that model again if your form uses a different model.
So here is my alternative approach.
In your controller's action that receives the posted form data. You can create a default CakePHP model, add some validation rules and then use that model for validation.
An example action might look like this;
function edit()
{
$model = ClassRegistry::init(array('class'=>'Email','table'=>false,'type'=>'Model'));
if(!empty($this->data))
{
$model->validate = array(
'subject'=>array(
'rule'=>'notEmpty',
'required'=>true
),
'message'=>array(
'rule'=>'notEmpty',
'required'=>true
)
);
if($model->save($this->data))
{
// validation was successful, but no data was actually saved
}
}
}
The key here is the creation of an automatic model by CakePHP.
$model = ClassRegistry::init(array('class'=>'Email','table'=>false,'type'=>'Model'));
The above attempts to find a model by Email in the applications model folder. When it is not found CakePHP will auto-create an in memory model for that class. Since we set the value of 'table' to false, then this should tell CakePHP that this model doesn't use a table.
This works as long as there really isn't a email.php file in the applications model folder. Once this model is created in memory. It's accessible from the built in Form help. That means validation errors will be passed correctly to the view.
Here is an example view file.
<?php echo $this->Form->create('Email',array('action'=>array('controller'=>'mycontroller','action'=>'edit'))); ?>
<?php echo $this->Form->input('subject'); ?>
<?php echo $this->Form->input('message',array('type'=>'textarea')); ?>
<?php echo $this->Form->submit(); ?>
The view will now render the validation errors from the Email model using the Form helper. This is because CakePHP class registry has saved a copy of the EMail auto model in memory that the Form helper will access.
If you want to use custom validation rules, then you will have to put the callback methods in the app_model.php file.
These methods tested in CakePHP 1.3
#ThinkingMedia's answer got me going in the right direction, but $model->save($this->data) was returning false for me unfortunately, even when the form was valid. I'm using CakePHP 2.3.9 for reference.
It turned out that even with 'table' => false parameter set, the returned$success of save() was based on a $count > 0 of the rows that were created/updated/modified. In my table-less case, this meant a $count of 0 and $success was false. I ended up blending the referenced answer with this similar question's solution to have validation work properly without a model file:
function edit()
{
$model = ClassRegistry::init(array('class'=>'YourFormName','table'=>false,'type'=>'Model'));
if($this->request-is('post'))
{
$model->validate = array(
'some_field'=>array(
'rule'=>'notEmpty',
'required'=>true
),
'another_field'=>array(
'rule'=>'notEmpty',
'required'=>true
)
);
$model->set($this->request->data)
if($model->validates($this->request->data) && empty($model->validationErrors))
{
// validation was successful, but no data was actually saved
}
}
}
Came across this question since I also had a similar issue. I have a form that needs to collect data and generate a PDF. So there is no data saving involved nor there is a maching model class. The PDF is a user contract and the user will fill the online form and the data filled will be used to generate the PDF which they must print and mail back. But I need to validate whether the fields are not empty, whether email is really an email format, and date inputs are really date inputs.
First I did without a model class then saw this quesion. Now I'm using a dummy model class to have my validations put in there since the code in controller action looks much neat.
Class Validation which is a subclass of Object is used by model class to perform validation against validation rules specified in it.
One can directly instantiate Validation class inside any controller or model and use its methods for performing validation on any data, not only inputs from forms.
I think my first question would be this: if you don't have a model...what are you validating? Typically data collection would be done to populate a model. If you're using an alternative data repository (file, web services, etc.), a model would still be the appropriate way to access and manipulate that data.
In short, in order to better answer this, I think a little more context would be helpful and maybe even necessary.