CakePHP 2 pagination gives 404 address not found error - cakephp

I am developing an application with CakePHP 2.3 and am having a problem with pagination. When I click on any of the pagination links I am getting a /cab/ServiceDirectory/refine/page:2 (or which ever link) was not found on this server. If I go to /cab/ServiceDirectory/refine I see the pagination links showing there should be 5 pages. If I remove the pagination code from the controller and the view I see all the results I should see.
In my ServiceDirectoryResultsController I have
public function index() {
$this->ServiceLocation->recursive=0;
}
public function refine ($id=null) (
// All my code to get the ServiceLocations from the DB
$this->paginate->array(
'conditions' => array('ServiceLocation.state' => $states[$state], 'ServiceLocation.solution_categories' => $active_cat),
'limit' => 8,
);
$results = $this->paginate('ServiceLocation');
$this->set('serviceLocation' ,$results);
}
In my View I have
<div class="paging">
<?php
echo $this->Paginator->prev('< ' . __('previous'), array(), null, array('class' => 'prev disabled'));
echo $this->Paginator->numbers(array('separator' => ''));
echo $this->Paginator->next(__('next') . ' >', array(), null, array('class' => 'next disabled'));
?>
</div>
<?php foreach ($serviceLocation as $Location): ?>
// I echo a few things from the $Location array
<?php endforeach; ?>
<div class="paging">
<?php
echo $this->Paginator->prev('< ' . __('previous'), array(), null, array('class' => 'prev disabled'));
echo $this->Paginator->numbers(array('separator' => ''));
echo $this->Paginator->next(__('next') . ' >', array(), null, array('class' => 'next disabled'));
?>
</div>
Without the paging and use doing a find all and sending that to the view I see the 40 results I would expect.
With the pagination in I see the pagination links and if I hover over them I see my url /cab/ServiceDirectoryResults/refine/page:2 or page:3 or page:4 or page:5
If I click on any of these links with the :pageX on them I get an error
Error: The requested address '/cab/ServiceDirectoryResults/refine/page:2 was not found on this server'
I have done a lot of reading about the CakePHP paging and can not find any reason for this behaviour. Can anyone suggest a reason or a possible solution or a path to follow to debug this error?
Regards
Richard
I checked the error level and it was already set to level 2
Here is the last entry in the error log
2013-05-22 17:09:28 Error: [NotFoundException] Not Found
Request URL: /cab/ServiceDirectoryResults/refine/page:2
Stack Trace:
#0 /var/www/html/cab/lib/Cake/Controller/Controller.php(1074): PaginatorComponent->paginate('ServiceLocation', Array, Array)
#1 /var/www/html/cab/app/Controller/ServiceDirectoryResultsController.php(381): Controller->paginate('ServiceLocation')
#2 [internal function]: ServiceDirectoryResultsController->refine()
#3 /var/www/html/cab/lib/Cake/Controller/Controller.php(486): ReflectionMethod->invokeArgs(Object(ServiceDirectoryResultsController), Array)
#4 /var/www/html/cab/lib/Cake/Routing/Dispatcher.php(187): Controller->invokeAction(Object(CakeRequest))
#5 /var/www/html/cab/lib/Cake/Routing/Dispatcher.php(162): Dispatcher->_invoke(Object(ServiceDirectoryResultsController), Object(CakeRequest), Object(CakeResponse))
#6 /var/www/html/cab/app/webroot/index.php(109): Dispatcher- >dispatch(Object(CakeRequest), Object(CakeResponse))
#7 {main}
Hope that helps

FYI: I'm using Cakephp 2.4, I've only tested it on this version.
I ran into this same issue. I store the pagination variables in the user's session.
This way when a user navigates away from a page, they don't loose their place (page/sort field/direction) when they come back to the page.
Because I store the variables in the session, then retrieve them from the session, if say the user clicks on a link that causes the results to be less then the page they were on, I'll get a 404.
Example (The page they were on):
http://localhost/products/index/page:5/sort:Product.name
I send them this link:
http://localhost/products/index/search:[search_term]
This filters the results to only 2 pages.
In this case, actual (perma)link, with the session variables restored, would look like:
http://localhost/products/index/page:5/sort:Product.name/search:[search_term]
Page 5 is out of the scope of the 2 pages from the new results.
This will throw the NotFoundException(); seen here:
http://api.cakephp.org/2.4/source-class-PaginatorComponent.html#238
How I resolved this was to catch the exception, then redirect them to the first page.
Since all of the pagination variables (including the 'search') are stored in the session, I only need to pass the page variable:
Example:
http://localhost/products/index/page:1
Cakephp recommended this as a possible way to overcome it:
http://book.cakephp.org/2.0/en/core-libraries/components/pagination.html#out-of-range-page-requests
However, I wanted this available for all of my 'index' pages, so I overwrote the Controller::paginate() method in the AppController.
Original paginate method seen here:
http://api.cakephp.org/2.4/source-class-Controller.html#1070-1081
Here is how I did it in my Controller/AppController.php:
<?php
class CommonAppController extends Controller
{
// ..... other code
public function paginate($object = null, $scope = array(), $whitelist = array())
{
/*
* Used to see if we get an exception thrown,
* If so, send them to the first page, and set the flash so they know
*/
try
{
// taken from their current paginate();
// http://api.cakephp.org/2.4/source-class-Controller.html#1070-1081
return $this->Components->load('Paginator', $this->paginate)->paginate($object, $scope, $whitelist);
}
catch (NotFoundException $e)
{
//Do something here like redirecting to first or last page.
//$this->request->params['paging'] will give you required info.
$page = (isset($this->request->params['named']['page'])?$this->request->params['named']['page']:1);
$this->Session->setFlash(__('Unable to find results on page: %s. Redirected you to page 1.', $page));
return $this->redirect(array('page' => 1));
}
}
// ..... other code
}
?>

8 months later...I just bumped into the same issue, and thought I could leave my findings here to avoid somebody else spending a couple of hours looking for a solution like I have been.
I had the same issue as Richard, when a GET request onto any other link of the pagination split bar, from 2 to n, was to generate a 404 error.
This 404 error originates from the PaginatorComponent (CORE/Cake/Controller/Component/PaginatorComponent.php), in these lines:
$pageCount = intval(ceil($count / $limit));
$requestedPage = $page;
$page = max(min($page, $pageCount), 1);
if ($requestedPage > $page) {
throw new NotFoundException();
}
In a nutshell, this happens when the predicative conditions member has become so restrictive as to create a much smaller recordset than for page #1.
What can create this is that it now contains more predicates in it, that you just do not want. A way to see what it contains for page #2+ is to add this call:
FirePHP($parameters);
just before the lines above. You may be surprised.
The question is "why does conditions, that you should build in your controller and pass to the find() method, has changed so much from page #1 that works, to page #2+ that does not?
I cannot just answer for you without seing the rest of your code, but I can tell you what was happening for my case:
I was storing some values in $_SESSION so that my search could be remembered, and I was relying on isset($this->request->data[$objectName]) to know if I should add that particular predicate to my 'conditions' array. But that was without thinking that only page #1 is the result of a POST method, not the subsequent paginated requests pages, which are got through GET method. Therefore from page #2 onwards, $this->request->data[$objectName] is empty, but SESSION was still set because I had forgottent to reset it.
The conclusion is: remember page #2+ calls are GET, and the request data that you may have used and were working for POST are no more valid, creating a different set of conditions now, reducing your recordset, and creating a total count that may be 0, voiding the pagination system, hence the 404.

I came across the same issue for CakePHP3. The quickest way to solve is to change the form action so that it do not grab the "?page=x" part of the URL. For example, change it to current page URL like
<form action="<?=\Cake\Core\Configure::::read('App.domain')?>/page_url" ...

Related

Paginator Not Found when page number specified

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) {
}

Cakephp URL while Paginating with search

I've a simple cakephp app with search and pagination. I'm using GET as type for forms.
When I click search with desired filters, paginated results are shown. I get desired results when I move across pages (with URL being modified with desired index of page number and filtered parameters).
Say I'm on page:4 of paginated search results. Now when I modify some search filter and hit search button again with page:4 shown, if results have less records (say that can be accommodated on one page), I get error - unable to find xxxxxx on server. (....../page:4/.... doesn't exist)..
Please help!
when you press search usually you want to reset all or same of the paginator parameters
I guess that in your view you just did
echo $this->Form->create('Model');
in this way cake assume that the action of the form is the url of the page you are in
but you can set the url of the form this way
echo $this->Form->create('Model', array('url' => array('page' => 1));
Resolved it as follows
$url = array('controller' => 'cc', 'action' => 'aa') + $this->request->params['pass'];
echo $this->Form->create(null, array('type' => 'get','url' => $url));
Thanks for your help.

blackhole cakephp 2 associated entities

My Goal:
Reuse of a contact form to be related to several different entities I call "Parents" ie Group has contact information, Member has contact info etc....
The way I tried doing it was:
1. Creating a view file for contact, named "form.ctp" which doesn`t create a new form, nor submits, just echo's the contact's fields.
2. Calling this file using requestAction
My Problem:
The form's _Token get crumbled.
Parent add.ctp example
<?php echo $this->Form->create('Group');?>
<fieldset>
echo $this->Form->input($field_prefix.'contact_id',array('type'=>'hidden'));
<?php echo $this->requestAction(array('controller' => 'contacts', 'action' => 'form'), array('named' => array('index'=>'0','parent'=>'Group',
'fields'=>array(
'email'=>array('value'=>'xx#yy.com','hidden'=>1)
))));
inside the form.ctp I have:
//Associated Model
echo $this->Form->input('Contact.0.city',array('type'=>'hidden'));
echo $this->Form->input('Contact.0.postcode');
echo $this->Form->input('Contact.0.phone');
echo $this->Form->input('Contact.0.cellphone');
echo $this->Form->input('Contact.0.email',array('value'=>""));
echo $this->Form->input('Contact.0.id',array('type'=>'hidden'));
?>
Looking at the HTML source code that is generated, I see that whether I use the request action or just copy the contect of the form.ctp into the "Parent's" add file, I get the same HTML result.
HOWEVER!!! when I use the form.ctp Action Request, I get the blackhole, the tokens are being messed up!!!
Any Ideas?
Thanks in advance
Orly
If your problem is solely reusing a form, you can use the form as a Element, and then you could call it multiple times, substituting in the exact values you need.
As for SecurityComponent, I would recommend (at least as a temporary fix) disabling SecurityComponent for that specific action by using $this->Security->unlockedActions(); in your controller's beforeFilter()

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.

cakePHP basic ajax form problem

i am trying to do a simple form that adds a new comment using ajax to a blog post (actually this is a part of a cakePHP tutorial)
but the problem is that the submit button do nothing at all
here is the part of code that generates the form in the view.ctp file
<?php echo $ajax->form('/comments/add', 'post', array('url' => '/comments/add', 'update' => 'PostsComments', 'indicator' => 'commentSaved'));?>
<fieldset>
<legend><?php __('Add Comment');?></legend>
<?php
echo $form->hidden('Comment.post_id', array('value' => $post['Post']['id']));
echo $form->input('Comment.name');
echo $form->input('Comment.content');
?>
</fieldset>
<?php echo $form->end('Submit');?>
and here is the add comment action in the comments_controller.php
if (!empty($this->data)) {
$this->Comment->create();
if ($this->Comment->save($this->data)) {
$comments = $this->Comment->find('all',array('conditions'=>array('post_id'=>$this->data['Comment']['post_id']),'recursive'=>-1));
$this->set(compact('comments'));
$this->render('add_success','ajax');
} else {
$this->render('add_failure','ajax');
}
}
the problem is that the action add isnt called from the view ... when i viewed the generated html source code i found something like that
<form id="form304217296" onsubmit="event.returnValue = false; return false;" method="post" action="/php-cake/blog/comments/add"><fieldset style="display:none;">
if i removed the on submit tag manually then the action is called but the add_success.ctp is generated as a new page not as an ajax call.
so what could be the problem ?
finally i have discovered the problem
there was more than one mistake in the tutorial (chapter 8 in CakePHP from novice to professional) after fixing those errors i found another problem is that cakephp v1.2.6 is not compatible with the latest version of prototype (v1.6.1)
as it gives the following error in firebug
Event.observe is not a function
so i used version 1.6.0.3 of prototype and the problem was solved.
for a list of mistakes in this chapter see this
Do you include prototype and scriptaculus in your View/Layout (both are needed for the Ajax-helper)? Also there might be an interference with jquery if you use that too

Resources