tag:blogger.com,1999:blog-1908110696125487216.post399593059540772902..comments2023-08-20T19:40:21.999+02:00Comments on Thanassis Bakalidis's Programming and DBA Scratch Pad: CakePHP 2.x: Saving paging data in the sessionAthanassios Bakalidishttp://www.blogger.com/profile/11722066740354628402noreply@blogger.comBlogger8125tag:blogger.com,1999:blog-1908110696125487216.post-33481665700175638212016-04-20T22:57:26.766+02:002016-04-20T22:57:26.766+02:00I've modified the code for compatibility with ...I've modified the code for compatibility with CakePHP 3.x. I've just started with CakePHP so don't know all the ins and outs yet, if anything could be improved I'm glad to hear of course:<br /><br /><br />namespace App\Controller\Component;<br /><br />use Cake\Controller\Component;<br />use Cake\Event\Event;<br /><br />/**<br /> * Pagination Recall CakePHP Component<br /> * CakePHP 3.x version. Thanassis Bakalidis abakalidis.blogspot.com=<br /> *<br /> * @author Thanassis Bakalidis, modified by TheFlyinGeek for compatibility with Cake 3.x<br /> * @version 3.0<br /> * @license MIT<br /> * @property SessionComponent $Sesion Session handler to save paging data into<br /> */<br />class PaginationRecallComponent extends Component {<br /> const PREV_DATA_KEY = 'Paginaion-PrevData';<br /><br /> private $_session = NULL;<br /> private $_controller = NULL;<br /> private $_action = NULL;<br /> private $_previousUrl;<br /><br /> public function initialize(array $config)<br /> {<br /> $this->_controller = $this->_registry->getController();<br /> $this->_action = $this->_controller->request->params['action'];<br /><br /> $this->_session = $this->request->session();<br /> }<br /><br /> public function startup(Event $event)<br /> {<br /> if ($this->_controller->name === 'CakeError')<br /> return;<br /><br /> $this->_restorePagingParams();<br /><br /> // save the current controller and action for the next time<br /> $this->_session->write(<br /> self::PREV_DATA_KEY,<br /> [<br /> 'controller' => $this->_controller->name,<br /> 'action' => $this->_action<br /> ]<br /> );<br /> }<br /><br /> private function _restorePagingParams()<br /> {<br /> $sessionKey = "Pagination.{$this->_controller->name}.{$this->_action}";<br /><br /> // extract paging data from the request parameters<br /> $pagingParams = $this->_extractPagingParams();<br /><br /> // if paging data exist write them in the session<br /> if (!empty($pagingParams)) {<br /> $this->_session->write( $sessionKey, $pagingParams);<br /> return;<br /> }<br /><br /> // no paging data.<br /> // construct the previous URL<br /> $this->_previousUrl = $this->_session->check(self::PREV_DATA_KEY)<br /> ? $this->_session->read(self::PREV_DATA_KEY)<br /> : [<br /> 'controller' => '',<br /> 'action' => ''<br /> ];<br /><br /> // and check if the current page is the same as the previous<br /> if ($this->_previousUrl['controller'] === $this->_controller->name &&<br /> $this->_previousUrl['action'] === $this->_action) {<br /> // in this case we have a link from our own paging::numbers() function<br /> // to move to page 1 pf the current page, delete any paging data<br /> $this->_session->delete($sessionKey);<br /><br /> return;<br /> }<br /><br /> // we are comming from a different page so if we have any session data<br /> if ($this->_session->check($sessionKey))<br /> // then restore and use them<br /> $this->_controller->request->query = array_merge(<br /> $this->_controller->request->query,<br /> $this->_session->read($sessionKey)<br /> );<br /> }<br /><br /> private function _extractPagingParams()<br /> {<br /> $pagingParams = $this->_controller->request->query;<br /><br /> $vars = ['page', 'sort', 'direction'];<br /> $keys = array_keys($pagingParams);<br /> $count = count($keys);<br /><br /> for ($i = 0; $i < $count; $i++)<br /> if (!in_array($keys[$i], $vars))<br /> unset($pagingParams[$keys[$i]]);<br /><br /> return $pagingParams;<br /> }<br />}TheFlyinGeekhttps://www.blogger.com/profile/06781263074254614250noreply@blogger.comtag:blogger.com,1999:blog-1908110696125487216.post-60900092360959095442015-07-29T02:04:16.087+02:002015-07-29T02:04:16.087+02:00Thanks for the component, it helped me a lot and d...Thanks for the component, it helped me a lot and do exactly what I need. The original component had the problem of the first page, but you figured it out how to do it. Thanks a lot.Unknownhttps://www.blogger.com/profile/14812956070685256803noreply@blogger.comtag:blogger.com,1999:blog-1908110696125487216.post-47586118131072465212014-09-09T13:49:23.429+02:002014-09-09T13:49:23.429+02:00'querystring'), but
* could easily be ext... 'querystring'), but<br /> * could easily be extended.<br /> * <br /> * The key problem arose with CakePHP version 2.4. From the migration guide:<br /> * <br /> * PaginatorHelper<br /> * The first page no longer contains /page:1 or ?page=1 in the URL. This helps prevent duplicate<br /> * content issues where you would need to use canonical or noindex otherwise.<br /> * <br /> * So, from 2.4 onwards, the problem is how to discriminate between a request for a new pagination<br /> * state of page:1, as opposed to a request to recall the saved (i.e. last) pagination state.<br /> * <br /> * Had this change at 2.4 (detailed above) _not_ occurred, the following algo would suffice:<br /> * <br /> * if pagination params in request {<br /> * store pagination params in session under key controller.action<br /> * }<br /> * elseif exists stored pagination params for this controller.action {<br /> * restore stored params into request<br /> * }<br /> * <br /> * The original version of this component (by thanassis) used a comparison of consecutive hits to<br /> * discriminate between the two cases, but this fails in a multiwindow or ajax environment. I also<br /> * considered using HTTP_REFERER but this also fails because a redirected request retains the original<br /> * referer.<br /> * <br /> * The solution below defines a special named parameter value, viz. page=0, which means "recall the<br /> * last saved pagination state", and, in the absence of this flag, we take it to be a request for a<br /> * new pagination state which needs to be saved (for later recall) and rendered.<br /> * <br /> * Note that the absence of pagination parameters (page, sort & direction) is itself a request for the<br /> * default pagination. The corollary is that we need to save the pagination state of _all_ actions, in<br /> * every controller, i.e., including all those (probably the vast majority) that do not do any pagination<br /> * at all! By an appropriate choice of session key we ensure that this does not end up clogging up the<br /> * session with irrelevant state data: at the end of a session, we will at maximum have a session record<br /> * for every controller in the system, and additional records keyed off these, one for each action which<br /> * actually uses pagination.<br /> */<br />class PaginationRecallComponent extends Component {<br /><br /> public $components = array('Session');<br /> private $_controller = NULL;<br /><br /> public function startup(Controller $controller)<br /> {<br /> $this->_controller = $controller; <br /> $sessionKey = "Pagination.{$this->_controller->name}.{$this->_controller->request->params['action']}";<br /><br /> // extract paging data from the request parameters<br /> $pagingParams = $this->_extractPagingParams();<br /> <br /> // If this is a recall request<br /> if (isset($pagingParams['page']) && $pagingParams['page'] === '0') {<br /> // If there is a saved state, restore it (otherwise, nothing to do<br /> // since PaginatorComponent::paginate() will correct page=0 to page=1<br /> // and the default pagination will be rendered).<br /> if (($lastParams = $this->Session->read($sessionKey)) !== null)<br /> $this->_controller->request->params['named'] = array_merge(<br /> $this->_controller->request->params['named'],<br /> $lastParams<br /> );<br /> } else // not a recall, save the params for the next recall<br /> if (count($pagingParams))<br /> $this->Session->write($sessionKey, $pagingParams);<br /> else // which amounts to deleting the last saved state request when the new<br /> // saved state is for a request for the default pagination.<br /> $this->Session->delete($sessionKey);<br /> }<br /> <br /> private function _extractPagingParams()<br /> {<br /> $pagingParams = $this->_controller->request->params['named'];<br /> if (!count($pagingParams)) return $pagingParams; // just to save time<br /><br /> $vars = array('page', 'sort', 'direction');<br /> $keys = array_keys($pagingParams);<br /> $count = count($keys);<br /><br /> for ($i = 0; $i < $count; $i++)<br /> if (!in_array($keys[$i], $vars))<br /> unset($pagingParams[$keys[$i]]);<br /><br /> return $pagingParams;<br /> }<br />}John Suttonhttps://www.blogger.com/profile/17819960316056685456noreply@blogger.comtag:blogger.com,1999:blog-1908110696125487216.post-58221496524598985522014-07-24T16:25:28.755+02:002014-07-24T16:25:28.755+02:00Hi,
Well I have been using this component for qui...Hi,<br /><br />Well I have been using this component for quite some time now. I resolved the issue I was previously having (first comment posted on this post) by detecting the 404 using a try/catch like so:<br /><br />try {<br /> $users = $this->Paginator->paginate();<br /> if (empty($users)) {<br /> $this->noResultRedirect();<br /> }<br /> $this->set(compact('users'));<br />} catch (NotFoundException $e) {<br /> $this->noResultRedirect();<br />}<br /><br />And in my AppController, the noResultRedirect() function looks like this:<br /><br />protected function noResultRedirect() {<br /><br /> $args = $this->passedArgs;<br /> if (isset($args["page"])) {<br /> $pageInArgs = $args["page"];<br /> }<br /> if ($this->Session->read("Pagination." . $this->modelClass . ".options.page")) {<br /> $pageInSession = $this->Session->read("Pagination." . $this->modelClass . ".options.page");<br /> }<br /><br /> if (isset($pageInArgs)) {<br /> $page = $pageInArgs;<br /> // ensure session is sync'd with args<br /> $this->Session->write("Pagination." . $this->modelClass . ".options.page", $page);<br /> } elseif (isset($pageInSession)) {<br /> $page = $pageInSession;<br /> } else {<br /> $page = 1;<br /> }<br /><br /> if ($page > 1) {<br /> // set page to 1 in args<br /> $args["page"] = 1;<br /> // set page to 1 in session<br /> $this->Session->write("Pagination." . $this->modelClass . ".options.page", 1);<br /><br /> // build url and redirect<br /> $args["controller"] = $this->params->controller;<br /> $args["action"] = $this->params->action;<br /> $redirect = Router::url($args);<br /> $this->redirect($redirect);<br /> }<br />}<br /><br />This works really well.<br /><br /><br />I have another question. I need to be able to store the pagination data relative to not just the controller, but also the action. <br /><br />So my thoughts would be to replace this:<br />$sessionKey = "Pagination.{$this->_controller->modelClass}.options";<br /><br />With this:<br />$sessionKey = "Pagination.{$this->_controller->modelClass}.{$this->_controller->request->params['action']}.options";<br /><br />This results in the session data being stored in a way that I think is usable, but I am stuck as to what else I need to change in the component code to get this session key to be read. <br /><br />Any help would be most appreciated,<br /><br />Thanks again f or a very useful component.<br /><br />MAnonymoushttps://www.blogger.com/profile/09193031278373319786noreply@blogger.comtag:blogger.com,1999:blog-1908110696125487216.post-27572383889419347272014-07-02T22:01:41.532+02:002014-07-02T22:01:41.532+02:00Thank you! This one helped me a lot. The orignal P...Thank you! This one helped me a lot. The orignal PaginationRecallComponent from the Bakery didn't work at all for me on a CakePHP 2.4.2 installation. Your version worked out of the box.Anonymoushttps://www.blogger.com/profile/13244489192845661142noreply@blogger.comtag:blogger.com,1999:blog-1908110696125487216.post-50708318914198660542014-05-17T11:14:47.851+02:002014-05-17T11:14:47.851+02:00Why not work with delete?Why not work with delete?Dvd74https://www.blogger.com/profile/04870336039169875191noreply@blogger.comtag:blogger.com,1999:blog-1908110696125487216.post-21528060994250354562014-02-15T10:46:50.424+02:002014-02-15T10:46:50.424+02:00@Mark Thanks for the comment.
Now, regarding your...@Mark Thanks for the comment. <br />Now, regarding your question about the filter plugin, I have never used it and so I cannot offer any real solution. <br /><br />The only remedy I can think of is to put filtering controls on the same page as your list. That way when you post a filtering request back to your controller action then the PaginationRecallComponent will determine that your previous URL is the same as your current and thus take you to page 1.Athanassios Bakalidishttps://www.blogger.com/profile/11722066740354628402noreply@blogger.comtag:blogger.com,1999:blog-1908110696125487216.post-33374491446874224452014-02-13T23:02:23.639+02:002014-02-13T23:02:23.639+02:00Brilliant. I was up until just now using you previ...Brilliant. I was up until just now using you previous PagingInfo code, but then stumbled across your SO post when faced with the same "page 1" issue.<br /><br />Might be worth adding an update and a link to your first post, in case any others land there first?<br /><br />One question, slightly off-topic. I am using your component with a Filter plugin https://github.com/lecterror/cakephp-filter-plugin<br /><br />They play very nicely together, except for in one case. If I filter my results down to a few pages, then navigate to page 2, then filter some more bringing the results to one page, I get a 404 because the paginator has remembered my previous page number (2), but that page contains no data as I have re-filtered. Hence the 404. <br /><br />My thoughts so far are for the any searches made using the filter plugin to reset the page number in the session, but I have as yet been unable to get this to work. You may not be able to help, but worth an ask, as I was here anyway.<br /><br />Thanks again for a great bit of code. <br />Anonymoushttps://www.blogger.com/profile/03484471046217802759noreply@blogger.com