Paginator Not Found when page number specified - cakephp

I manually set the CakePHP Pagination values in my Usergals Controller like so, so as to Paginate a related Model (TreasuresUsergal) on the view of Usergal. Here is a simplified snippet from the UsergalController:
public function view($id = null) {
$this->loadModel('TreasuresUsergal');
$options['joins'] = array(
array('table' => 'treasures',
'alias' => 'Treasure2',
'type' => 'LEFT',
'conditions' => array(
'Treasure2.id = TreasuresUsergal.treasure_id',
)
)
);
$options['conditions']=array('TreasuresUsergal.usergal_id'=>$id);
$options['fields']=array('Treasure2.*','TreasuresUsergal.ord','TreasuresUsergal.comments');
$options['order']=array('TreasuresUsergal.ord'=>'asc');
$options['limit']=$limit;
$this->Paginator->settings = $options;
$treasures=$this->Paginator->paginate('TreasuresUsergal');
$this->set('treasures',$treasures);
}
So in the above example, $id is the value passed to the view function from the URL. There is a live example of this here:
http://collections.centerofthewest.org/usergals/view/20
As you can see, it works just fine for a single page. However, today I tested the Paginator in the view and discovered the "next" button does not work. The Counter, sorting, and Page numbers all load correctly - but anytime the actual named parameter "page:n" is passed (when n is greater than 1) I get a Not Found page with the following error:
Not Found
Error: The requested address '/usergals/view/20/page:2?url=%2Fusergals%2Fview%2F20' was not found on this server.
I must be missing something simple - I have experimented with the routes a little, but haven't been able to figure it out. Or perhaps I am missing some Paginator options? Or does it think its OutOfBounds when its not?
UPDATE / WORKAROUND
After some messing around, I have devised this workaround. Not as nice as I'd like, but here is the basic idea (error handling, etc can be added)
First, I added a check in beforeFilter to see if page paramter was set. If so, I change it to 'p' parameter and redirect.
I did this here because otherwise I had problems with the Not Found exception (see notes at bottom). So, in beforeFilter:
if (isset($this->params['named']['page'])){
$newurl=$this->params['named'];
$pg=$newurl['page'];
unset($newurl['page']);
$newurl['p']=$pg;
$this->redirect(array('action' => 'view/'.$this->params['pass'][0])+$newurl);
}
Then, in the 'view' function of the same controller, I added this along with the other Paginator options:
if (isset($this->params['named']['p'])) $options['page']=$this->params['named']['p'];
With this, the standard Paginator behavior seems to work fine in the view. Prev, next, etc.
If anyone has a better suggestion, I would love to hear it. I don't like the idea of having to redirect, but it works for now.
It's worth noting that adding this code (even just to experiment) - caused all of my pagination counts to stop working. The query in debug was correct, but the displayed counts were wrong:
try {
$this->Paginator->paginate();
} catch (NotFoundException $e) {
}

Related

Pagination links breaking search results coming from post data cakephp

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.

Pagination in requestAction

I'm building a dynamic view (Page) that consists of multiple elements (widgets) called via $this->element('messages_unread'). Some of these elements need data that is not related to the Page model.
In real life words: my users will be able to construct their own Page by choosing from a multitude of elements ("top 5 posts", "10 unread messages", etc...)
I get the data by calling $this->requestAction(array('controller'=>'events','action'=>'archive') from within the element, the url-variables differ per element .
I'm aware of the fact that requestAction() is expensive and I plan on limiting the costs by proper caching.
The actual question:
My problem is Pagination. When I'm in the Page view and call requestAction('/events/archive') the PaginatorHelper in the Page view will be unaware of the Event model and its paginator variables and $this->Paginator->next() etc... will not work.
How can I implement proper Pagination? I've tried to set the model by calling $this->Paginator->options(array('model'=>'Event')) but that doesn't work.
Do I maybe need to return custom defined Pagination variables in the requestAction and thus construct my own?
Or is there another approach that maybe even avoids requestAction()? And keep in mind here that the requested data is unrelated to the Page.
Kind regards,
Bart
[Edit] My temporary solution but still open for comments/solutions:
In the requestedAction Event/archive, return paginator variables along with the data like this:
return array('data'=>$this->paginate(), 'paging' => $this->params['paging']);
I've tinkered a bit more and the following works for me, and the PaginationHelper works:
In the element:
// requestAction returns an array('data'=>... , 'paging'=>...)
$data = $this->requestAction(array('controller'=>'events','action'=>'archive'));
// if the 'paging' variable is populated, merge it with the already present paging variable in $this->params. This will make sure the PaginatorHelper works
if(!isset($this->params['paging'])) $this->params['paging'] = array();
$this->params['paging'] = array_merge( $this->params['paging'] , $data['paging'] );
foreach($data['events'] as $event) {
// loop through data...
}
In the Controller:
public function archive() {
$this->paginate = array(
'limit' => 10
);
if ($this->params['requested'])
return array('events'=>$this->paginate('Event'), 'paging' => $this->params['paging']);
$this->set('events', $this->paginate('Event') );
}

Cakephp Paginate Find

I want to list the posts of a given user. It work but paginate is not accurate.
My code is the following
public function index($userid = null) {
if ($this->Post->exists($userid)) {
$this->set('posts',$this->Post->find('all',array('conditions'=>array('user_id'=>$userid))),
$this->paginate());
} else
{
$this->Post->recursive = 0;
$this->set('posts', $this->paginate());
}
The result give the correct list --> 3 posts, but the paginator display page number 1 and 2
Can you help me?
Thank you
Refer to the documentation
The code in the question is quite confused.
find
The find method only has two parameters:
find(string $type = 'first', array $params = array())
The third parameter (the result of calling paginate) isn't used and will be ignored - but it will setup the view variables for the pagination helper, based on the conditions used in the paginate call - there are no conditions being used.
It is not possible to paginate the result of a find call - to do so restructure the code to call paginate instead of find.
paginate
The paginate method is just a proxy for the paginator component - it can be used in several ways, this one (controller code example):
$this->paginate($conditions)
Is the most appropriate usage for the case in the question i.e. the complete action code should be similar to:
public function index($userId = null) {
$conditions = array();
if ($userId) {
$conditions['Post.user_id'] = $userId;
}
$this->set('posts',$this->paginate($conditions));
}
Note that logically, if a user id is requested that doesn't exist the response should be nothing - not everything.
I'm quite sure that conditions for paginate do now work that way.
If you want to set conditions for paginations you should do it as follows:
$this->paginate = array('conditions' => array('Post.user_id' => $userid)));
$this->set('posts', $this->paginate());
And yes, the result stored in $posts ( in view ) will be proper as you assigned proper find result to it, meanwhile you've paginated post model without any conditions whatsoever.
First off, you're checking to see if the post exists but using the $userid. Are you trying to see "if the user exists, get the posts for that user, or else get posts for ALL users"? As you have it right now, say you have the $userid = 159, but the max Post.id in your database is 28, then the condition is not being met because it is checking to see whether or not there is a Post with the id = 159 that exists, which it doesn't.
Second, your conditions are wrong. You are performing a find and then a paginate which are two separate queries. The conditions are being implemented on the find query but not the paginate but you are only displaying the find results.
public function index($userid = null) {
// setting recursive outside of if statement makes it applicable either way
$this->Post->recursive = 0;
// check if user exists
if ($this->Post->User->exists($userid)) {
// get posts for user
$this->set('posts', $this->paginate('Post', array('Post.user_id' => $userid));
}
else{
// get all posts
$this->set('posts', $this->paginate('Post'));
}
} // end index function

Pagination and sort - Plugin not working - CakePHP 2.0

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());
}

CakePHP strange behavior with beforeFilter: I cannot set the variables to the view

Okay, this will require some setup:
I'm working on a method of using nice post title "slugs" in the URL's of my cakePHP powered blog.
For example: /blog/post-title-here instead of /blog/view_post/123.
Since I'm obviously not going to write a new method for every post, I'm trying to be slick and use CakePHP callbacks to emulate the behavior of PHP 5's __call() magic method. For those who do not know, CakePHP's dispatcher checks to see if a method exists and throws a cakePHP error before __call() can be invoked in the controller.
What I've done so far:
In the interest of full disclosure ('cause I have no Idea why I'm having a problem) I've got two routes:
Router::connect('/blog/:action/*', array('controller' => 'blog_posts'));
Router::connect('/blog/*', array('controller' => 'blog_posts'));
These set up an alias for the BlogPostsController so that my url doesn't look like /blog_posts/action
Then in the BlogPostsController:
public function beforeFilter() {
parent::beforeFilter();
if (!in_array($this->params['action'], $this->methods)) {
$this->setAction('single_post', $this->params['action']);
}
}
public function single_post($slug = NULL) {
$post = $this->BlogPost->get_post_by_slug($slug);
$this->set('post', $post);
//$this->render('single_post');
}
The beforeFilter catches actions that do not exist and passes them to my single_post method. single_post grabs the data from the model, and sets a variable $post for the view.
There's also an index method that displays the 10 most recent posts.
Here's the confounding part:
You'll notice that there is a $this->render method that is commented-out above.
When I do not call $this->render('single_post'), the view renders once, but the $post variable is not set.
When I do call $this->render('single_post'), The view renders with the $post variable set, and then renders again with it not set. So in effect I get two full layouts, one after the other, in the same document. One with the content, and one without.
I've tried using a method named single_post and a method named __single_post and both have the same problem. I would prefer the end result to be a method named __single_post so that it cannot be accessed directly with the url /blog/single_post.
Also
I've not yet coded error handling for when the post does not exist (so that when people type random things in the url they don't get the single_post view). I plan on doing that after I figure out this problem.
This doesn't explicitly answer your question, but I'd just forego the whole complexity by solving the problem using only routes:
// Whitelist other public actions in BlogPostsController first,
// so they're not caught by the catch-all slug rule.
// This whitelists BlogPostsController::other() and ::actions(), so
// the URLs /blog/other/foo and /blog/actions/bar still work.
Router::connect('/blog/:action/*',
array('controller' => 'blog_posts'),
array('action' => 'other|actions'));
// Connect all URLs not matching the above, like /blog/my-frist-post,
// to BlogPostsController::single_post($slug). Optionally use RegEx to
// filter slug format.
Router::connect('/blog/:slug',
array('controller' => 'blog_posts', 'action' => 'single_post'),
array('pass' => array('slug') /*, 'slug' => 'regex for slug' */));
Note that the above routes depend on a bug fix only recently, as of the time of this writing, incorporated into Cake (see http://cakephp.lighthouseapp.com/projects/42648/tickets/1197-routing-error-when-using-regex-on-action). See the edit history of this post for a more compatible solution.
As for the single_post method being accessible directly: I won't. Since the /blog/:slug route catches all URLs that start with /blog/, it'll catch /blog/single_post and invoke BlogPostsController::single_post('single_post'). You will then try to find a post with the slug "single_post", which probably won't exist. In that case, you can throw a 404 error:
function single_post($slug) {
$post = $this->BlogPost->get_post_by_slug($slug);
if (!$post) {
$this->cakeError('error404');
}
// business as usual here
}
Error handling: done.

Resources