In Rails, we can specify the allowed parameters to be used in the controller when saving data. So, with params being the submitted data, I can do this:
params.require(:person).permit(:name, :age)
Which will ensure that the :person key is present and will filter out anything that is not a person's :name or :age.
Is there any way in CakePHP to accomplish this?
EDIT: I know I can write PHP, I want to know if there's a Cake component / plugin that already does this.
Something in this PHP way:
// submited data
$this->request->data['Person'] = array(
'name' => 'Salines',
'age' => '41',
'job' => 'Web Developer'
);
// check if isset and filter out anything that is not a person's name or age
if(isset($this->request->data['Person']))
{
$permit = array('name' => '','age' => '');
$this->request->data['Person'] = array_intersect_key($this->request->data['Person'],$permit);
}
//and return $this->request->data like
array(
'Person' => array(
'name' => 'Salines',
'age' => '41'
)
);
I'm looking for a Cake-provided solution (if there is one)
Well, define "cake-provided", you mean by the framework itself? No, the core doesn't have this functionality but there are two plugins.
For Cake3: Plum Search
For Cake2 & 3: CakeDC Search
For Cake3 I would go for Plum-Search, it is written by the same person as the initial code of the other plugin but a complete rewrite and makes a better use of Cake3.
Next time you ask name your exact Cake version.
Both plugins implement the PRG pattern but don't explicitly allow or deny query parameters. They'll only grab the parameters you specified in your filter declaration and turn them into the request. Validate and save to exclude unwanted fields.
Make a url link like this
echo $this->Html->url(array('controller'=>'users','action'=>'hello','par1'=>23,'par2'=>'sud'));
In hello function in users controller
pr($this->params->named['par1']);
pr($this->params->named['par2']);
Related
I am using a cakephp 2.5 postlink method inside a view file :
$tableRow['Model.modelatribute'] = $this->Form->postLink(
$data['Vehicle']['plate'],
array('controller'=>'somecontroller',
'action' => 'somemethod',
'Model.modelatribute' => base64_encode($data['Vehicle']['plate'])
),
array('confirm' => 'Look at vehicle '.$data['Vehicle']['plate'])
);
I would like not to show the model atribute name on the url bar. After clicking on the link and being redirected, the url shows :
somemethod/Model.modelatribute:vSpEeTIweQ%3D%3D
Can i hide the model atribute name, using postlink method o cakephp 2.5 ?
ThankĀ“s in advance
If you simply want to pass the value of Model.modelatribute as a parameter, just leave off Model.modelatribute in your routing array. If you want to pass the value without occouring in the url you can use the data option of postLink.
echo $this->Form->postLink(
$data['Vehicle']['plate'],
array(
'controller'=>'somecontroller',
'action' => 'somemethod',
base64_encode($data['Vehicle']['plate']) // Routing array without modelname
),
array(
'confirm' => 'Look at vehicle '.$data['Vehicle']['plate'],
'data' => array(
// Data option of postLink method
'Model.modelatribute' => base64_encode($data['Vehicle']['plate'])
)
)
);
Well, you put in there. If you don't want it to be there simply don't put it in the URL. You can alternatively put it into the body of the POST request.
If you're concerned that people can use the model name for something and you want to hide it for that reason, then it is a very bad approach called security through obscurity. Instead you want to ensure that you validate that only values you want are passed and processed. So check your incoming data, always, from everywhere.
I've read that, to be able to rank search results you may query MySQL like this:
SELECT * ,
MATCH (title, body) AGAINST ('$search') AS rating
FROM posts
WHERE MATCH (title, body) AGAINST ('$search')
ORDER BY rating DESC
Is there a way to do this in CakePHP 2.X?
Also, I need to do this while paginating at the same time. So I think I would need to write condition for the paginator, not a direct 'query'.
Thanks for your help!
Use like this it will prevent mysql injection too
array("MATCH(User.current_position) AGAINST(? IN BOOLEAN MODE)" => $srch_arr['text'])
Ok, it took me some time... Since, the key issue was to get a rating on the resulting matches, the complicated part in this query was the specific field:
MATCH (title, body) AGAINST ('$search') AS rating
I figured that I should just write that field in the "field" option, in the pagination array.
The resulting code was the following:
$this->paginate = array(
'limit' => 15,
'fields' => array('*', "MATCH (data) AGAINST ('$q') AS rating"),
'conditions' => "MATCH(SearchIndex.data) AGAINST('$q' IN BOOLEAN MODE)",
'order' => array(
'rating' => 'desc',
),
);
$paginatedResults = $this->paginate('SearchIndex');
And that worked seamlessly!
I think this is the best way to achieve real search results using Cake. Unless someone has a better alternative :)
Searching phrases in between double quotes will give you the results you should expect!
I have used the above database call by Thomas (thank you) and it does work seamlessly.
However the code:
'conditions' => "MATCH(SearchIndex.data) AGAINST('$q' IN BOOLEAN MODE)",
removes the Data Abstraction Layer and opens up your site to SQL injection.
It's probably not quite as good (haven't fully tested it) but try:
'SearchIndex.data LIKE'=>'%'.$search.'%'
I hope this is helpful in someway.
I wanted to now if there is a way to pass a simple string inside url and cakephp some how put it inside one of the inputs of my form without any code writing on view side?
I tried calling this->set("bla","bla"); the field name is bla
but nothing changed in view
As I understood the question you want to have something like this:
in the url you have something like:
http://yourserver.com/controller/action?search=string
and you want to put this "string" in the search field, right?
Then let's imagine that your field's name is data[search_field]. (I am skipping the model, because for my example it's not needed, but it's possible the name to be data[Model][search_field]).
Then in your controller's action you have to do following:
$this->data['search_string'] = $this->params['url']['search'];
You can pass values in the url by using html helper. Try:
echo $this->Html->link('View Some Page', array(
'controller' => 'yourController',
'action' => 'view',
1, // id
'?' => array('yourField' => 'valueToPass'))
);
I am working on a book review application and I am using autoComplete to search for titles in when creating a review. The review model has an associated book_id field and the relationship is setup as the review hasOne book and a book hasMany reviews.
I am trying to pass the Book.id (into the book_id field), but I want to display Book.name for the user to select from. With the default setup (accomplished via CakePHP's tutorial), I can only pass Book.name. Is it possible to display the name and pass the id?
Also, I am passing it via the following code in the create() action of the review controller:
$this->data['Review']['book_id'] = $this->data['Book']['id'];
Is that the proper way to do it in CakePHP? I know in Ruby on Rails, it is automatic, but I can't seem to make it work automagically in CakePHP. Finally, I am not using the generator because it is not available in my shared hosting environment... so if this is the wrong way, what do I need other than associates in my models to make it happen automatically?
Thanks for the help and I promise this is my question for awhile...
UPDATE- I tried the following, but it is not working. Any ideas why?
function autoComplete() {
$this->set('books', $this->Book->find('all', array(
'conditions' => array(
'Book.name LIKE' => $this->data['Book']['name'].'%'
),
'fields' => array('id','name')
)));
$this->layout = 'ajax';
}
The problem is that when I use the code above in the controller, the form submits, but it doesn't save the record... No errors are also thrown, which is weird.
UPDATE2:
I have determine that the reason this isn't working is because the array types are different and you can't change the array type with the autoComplete helper. As a workaround, I tried the follow, but it isn't working. Can anyone offer guidance why?
function create() {
if($this->Review->create($this->data) && $this->Review->validates()) {
$this->data['Review']['user_id'] = $this->Session->read('Auth.User.id');
$this->Book->find('first', array('fields' => array('Book.id'), 'conditions' => array('Book.name' => $this->data['Book']['name'])));
$this->data['Review']['book_id'] = $this->Book->id;
$this->Review->save($this->data);
$this->redirect(array('action' => 'index'));
} else {
$errors = $this->Review->invalidFields();
}
}
FINAL UPDATE:
Ok, I found that the helper only takes the find(all) type or array and that the "id" field wasn't passing because it only applied to the autoComplete's LI list being generated. So, I used the observeField to obtain the information and then do a database lookup and tried to create a hidden field on the fly with the ID, but that didn't work. Finally, the observeField would only take the characters that I put in instead of what I clicked, due to an apparent Scriptaculous limitation. So, I ended up going to a dropdown box solution for now and may eventually look into something else. Thanks for all of the help anyway!
First of all, $this->data will only contain ['Book']['id'] if the field exists in the form (even if it's hidden).
To select something by name and return the id, use the list variant of the find method, viz:
$selectList = $this->Book->find('list', array(
'fields' => array(
'id',
'name'
)));
$this->set('selectList', $selectList);
In the view, you can now use $selectList for the options in the select element:
echo $form->input('Book.id', array('type' => 'hidden'));
echo $form->input('template_id', array(
'options' => $selectList,
'type' => 'select'
));
I have a Projects table and a Users table which are linked by a HABTM relation. In the "add" new Project page I have a multiple checkbox section to select Users for the new project. I want to have at least one User for the Project. What's the best way to approach this in CakePHP ?
Try this:
// app/models/project.php
/**
* An additional validation check to ensure at least one User is
* selected. Spoofs Cake into thinking that there are validation
* errors on the Project model by invalidating a non-existent field
* on the Project model, then also invalidates the habtm field as
* well, so when the form is re-displayed, the error is displayed
* on the User field.
**/
function beforeValidate() {
if (!isset($this->data['User']['User'])
|| empty($this->data['User']['User'])) {
$this->invalidate('non_existent_field'); // fake validation error on Project
$this->User->invalidate('User', 'Please select at least one user');
}
return true;
}
I stumbled on the same issue, but now - 3 years later - with CakePHP 2.3.
To be clear; Group has and belongs to User. I've had a form like this:
// View/Groups/add.ctp
echo $this->Form->input('name');
echo $this->Form->input('User');
With the validation rule like in user448164's answer:
// Model/Group.php
public $validate = array(
'User' => array(
'rule' => array('multiple', array('min' => 1)),
'message' => 'Please select one or more users'
)
);
That didn't work, after Googling for it, I found this question which couldn't still be the best solution. Then I tried several things, and discovered this to work just fine:
// View/Groups/add.ctp
echo $this->Form->input('name');
echo $this->Form->input('Group.User');
Way too easy solution, but had to dig into it to find out it works this way.
Hopefully it helps somebody one day.
Update for CakePHP 2.4.x (possibly 2.3.x as well)
When I wrote this answer, I was using CakePHP 2.3.x. Back then it worked perfectly for both validating and saving the data. Now when applying the same code on a new project, using CakePHP 2.4.x, it didn't work anymore.
I created a test case, using the following code:
$data = array(
'User' => array(
'Client' => array(8)
),
);
$this->User->create();
$this->User->saveAll($data);
My first thought was: Saving all means saving all "root" models, what actually makes sense to me. To save deeper than just the "root" ones, you'll have to add the deep option. So I ended up with the following code:
$data = array(
'User' => array(
'Client' => array(8)
),
);
$this->User->create();
$this->User->saveAll($data, array('deep' => true));
Works like a charm! Happy coding. :)
Update (2014/03/06)
Struggling with the same problem again, in this case with hasMany instead of habtm. Seems like it behaves the same way. But I found myself looking for this answer again, and got confused.
I'd like to make clear that it's key to use Group.User instead of User in your input. Else it won't use the User model validation.
I've just been looking at his problem myself on a project and came across a slightly more elegant solution, as long as you're only dealing with a habtm relationship and you need to ensure that at least one checkbox is selected.
so for example you're editing a Project and you want it to be associated with at least one user
Add this to beforeValidate()
// check habtm model and add to data
foreach($this->hasAndBelongsToMany as $k=>$v) {
if(isset($this->data[$k][$k]))
{
$this->data[$this->alias][$k] = $this->data[$k][$k];
}
}
In the validation rules add the following:
'User' => array(
'rule' => array('multiple', array('min' => 1)),
'message' => 'Please select one or more users'
)
teknoid's blog has a pretty in depth solution to your issue here. The most Cakey way of doing this would be to add custom validation to your model, as you mention in your comment above. Check out http://teknoid.wordpress.com/2008/10/16/how-to-validate-habtm-data/
From the article, where Tag HABTM Post (:: Project HABTM Users):
First, we validate the Tag model, by
using the data from the form to ensure
that at least one Tag was selected. If
so, we save the Post and the relevant
Tags.
2016 update for CakePhp 2.7
Full answer here : HABTM form validation with CakePHP 2.x
TL;DR;
AppModel.php
public function beforeValidate($options = array()){
foreach (array_keys($this->hasAndBelongsToMany) as $model){
if(isset($this->data[$model][$model]))
$this->data[$this->name][$model] = $this->data[$model][$model];
}
return true;
}
public function afterValidate($options = array()){
foreach (array_keys($this->hasAndBelongsToMany) as $model){
unset($this->data[$this->name][$model]);
if(isset($this->validationErrors[$model]))
$this->$model->validationErrors[$model] = $this->validationErrors[$model];
}
return true;
}
In the main model of your HABTM :
public $validate = array(
'Tag' => array(
'rule' => array('multiple', array('min' => 1)),
'required' => true,
'message' => 'Please select at least one Tag for this Post.'
)
);
If you are using CakePHP 2.3.x, you may need to add this code to your model in addition to the code that GuidoH provided, otherwise your HABTM model data may not save:
public function beforeSave($options = array()){
foreach (array_keys($this->hasAndBelongsToMany) as $model){
if(isset($this->data[$this->name][$model])){
$this->data[$model][$model] = $this->data[$this->name][$model];
unset($this->data[$this->name][$model]);
}
}
return true;
}
As per my comment on Guido's answer above, I use Guido's answer exactly, however I modify the data with the beforeSave callback before it saves to the database.
I have this issue on Cake 2.4.5+
public function beforeSave($options = array()) {
$temp = $this->data['Group']['User'];
unset($this->data['Group']['User']);
$this->data['User']['User'] = $temp;
return true;
}