When I've Search my listing i'm getting some results with pagination, but when i go for second page my search is
breaking as it was a get request where i'm getting the search results via post method.
Note: For getting search results I don't want to submit the form via get request (i.e. Query string params) and also don't want to store the form data in session
Is there any way to get the results which satisfy the above conditions ?
You want to implement the PRG Pattern.
Post/Redirect/Get (PRG) is a web development design pattern that
prevents some duplicate form submissions, creating a more intuitive
interface for user agents (users). PRG implements bookmarks and the
refresh button in a predictable way that does not create duplicate
form submissions.
The CakeDC Search plugin makes that pretty easy to do in CakePHP.
It would be very hard to do it using only "POST" calls. You'll need to transfor your POST into a GET call.
Check this post i made or clone it from github
Hope this helps
EDIT:
Using my git repo. If you want url querystrings instead of named parameters:
in this line, instead of the foreach build the querystring and pass it to the redirect
in this line, get the parameters from the query string ($GET)
and in this line add page, sort and direction to this->paginate
I haven't tested it, but it should be something like that
We can do it with a patch.
In Views :
create search form :
$this->Form->create('Search', array('url' => array('controller' => 'controller', 'action' => 'index', substr(time(), 2,rand(1, 7) ))) );
Note : A random number appended at the end of the form action. This will let us know when to clear session.
in Controller :
public function index( $search = null)
{
$conditions = array(1 => 1);
if( !empty($this->data['Search']['keyword']) && $search)
{
$conditions = array('Model.field like' => $this->data['Search']['keyword'] . '%');
// store search array in session
$this->Session->write('conditions', $this->data['Search']);
}
if ($search)
{
$this->request->data['Search'] = $this->Session->read('conditions');
$conditions = array('Model.field like' => $this->data['Search']['keyword'] . '%');
}
else
{
$conditions = array(1 => 1);
$this->Session->delete('conditions');
}
$this->paginate= array('limit'=> 10, 'conditions' => $conditions);
$lists = $this->Paginate('Model');
}
Hope you understand the logic behind.
Related
I am trying to add a function to that strips the html from a field in drupal views. I found a function for sql server called "udf_StripHTML" that does that.
http://blog.sqlauthority.com/2007/06/16/sql-server-udf-user-defined-function-to-strip-html-parse-html-no-regular-expression/
I am using the following code:
/**
* Implements hook_views_query_alter().
*/
function cviews_views_query_alter(&$view, &$query) {
// Add a strip html tags from content.
$fields = array('field_data_body.body_value');
foreach ($query->where as $key1 => $value) {
foreach ($value['conditions'] as $key2 => $coditions) {
if (in_array($coditions['field'], $fields)) {
$query->where[$key1]['conditions'][$key2]['field'] = 'dbo.udf_StripHTML(' . $coditions['field'] . ')';
}
}
}
}
When views module converts the query object to a string the field become
from:
'dbo.udf_StripHTML(field_data_body.body_value)';
to:
[dbo].[udf_StripHTMLfield_data_body.body_value]
My question is, how can I add a function there?
Thank you,
You are going way too deep here friend. I'm going to assume that you're using Drupal 7, but for Drupal 8 this should be similar (since views is in core for both).
A few things about your approach:
That function is a user defined function which means that it needs to be defined at a much lower-level (in the SQL database) before you can use it in your query.
This is a red-herring approach, however, because you don't need to even touch the SQL to accomplish what you want (you can do this with PHP with strip_tags!)
You don't need a query alter hook here (we don't need to go to the database to do this). You could do this with one of the preprocess or field hooks from the field API or the views API using the function linked in my previous point.
Even better, you don't even have to touch the code to accomplish this. You can do it right in the Drupal UI.
Under the field settings for the view, select rewrite results and then Strip HTML tags. Presto, no more HTML tags in that field.
Image source: https://www.drupal.org/node/750172
Here is the solution that worked for me:
// Traverse through the 'where' part of the query.
foreach ($query->where as &$condition_group) {
foreach ($condition_group['conditions'] as &$condition) {
if (in_array($condition['field'], $fields)) {
$value = $condition['value'];
$field = $condition['field'];
$condition = array(
'value' => array(),
'field' => t('dbo.udf_StripHTML(!field) like \'#value\'', array(
'!field' => $field,
'#value' => $value)),
'operator' => 'formula',);
}
}
}
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']);
I have created a plugin called movies, I have used custom routes.
I have used pagination limit as '5'.
The first page is fine, but when I click on next or numbers. Those things doesn't works.
URL: something.com/movieslist/2
my Plugin/Movies/Config/routes.php
Router::connect('/movieslist', array('plugin' => 'Movies', 'controller' => 'Movies', 'action' => 'index'));
Router::connect('/movieslist/:page', array('plugin' => 'Movies', 'controller' => 'Movies', 'action' => 'index'));
my action code: Plugin/Movies/Controller/MoviesController.php
public function index() {
$this->Movie->recursive = 0;
$this->paginate = array('limit'=>5);
$this->set('movies', $this->paginate());
}
my view file code: Plugin/Movies/View/Movies/index.ctp
Same one from cakebake console. No changes made here.
Even the sort doesn't works :(
I'm tiered of searching my problems in many places :(
I had previously getting error in links itself and I fixed this after seeing this page:
CakePHP custom route pagination
Links are fixed but the links doesn't works. Pls don't down vote, I'm struggling from long time.
I'm using cakephp 2.0 version.
As a first step I'd swap the order of the router rules, since cakephp stops after the first match found. While the "/movieslist/:page" one will match a url with a page, it only does so for the named parameter page. Without it a url of the form "/movielist/2", might be interpreted as a link to "/movielist" with a normal parameter "2", hence the first router rule triggers.
If that does not work you can always just manually set the named page parameter. paginate just looks to see if it is set, but does not care if cakephp automagically figured it out from the url or if you do it yourself.
public function index($myPage=1) {
$this->Movie->recursive = 0;
$this->paginate = array('limit'=>5);
$this->params["page"] = $myPage;
$this->set('movies', $this->paginate());
}
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;
}