The PaginationRecall component is giving me a headache, it let my application crash.
I have a region view, I can call it with /views/[number of region]
I found out that the reason for the crash is that it wants to go back to Page:5
although the url is changed to a different region which only has 1 or 2 pages.
I updated to the latest cakePHP version [v2.3.5] as there was a mention about a fix for 'Preventing pagiation limit from overflowing the max integer value' But updating didn't solve my problem. Not sure if this is a problem with the PaginationRecal component or the Paginator itself.
For example; I can browse to the url ../regions/view/1/page:5 leave that page and when return back all fine [it remembered last page]
But when changing the url to a different region ../regions/view/56/ it crashes with error: Error: The requested address '/regions/view/56' was not found on this server.
Stack Trace
CORE/Cake/Controller/Controller.php line 1074
#deprecated Use PaginatorComponent instead
*/
public function paginate($object = null, $scope = array(), $whitelist = array()) {
return $this->Components->load('Paginator', $this->paginate)->paginate($object, $scope, $whitelist);
}
At first I didn't understand this crash untill I found out it was related to the page number. The problem never pops-up when browsing only on the first page. How can I fix this problem?
I have added the PaginatorRecall Component in my /app/Controller/AppController.php
public $components = array('PaginationRecall');
This is the component: http://bakery.cakephp.org/articles/Zaphod/2012/03/27/paginationrecall_for_cakephp_2_x
Any help much appreciated.
SOLVED
After investigating what the PaginatorRecall component is doing, I found out that it filters out only the page:number and stores it in a session.
I have added a rule to find out if the region is changed and if so then reset the page to page:1
Hope this helps someone else using this component as well.
This is my solution to this problem and the bug that PaginationRecall have with the first page. Surely it is not the most optimal solution, but it is functional.
class PaginationRecallComponent extends Component {
var $components = array('Session');
var $Controller = null;
function initialize(&$controller) {
$this->Controller = & $controller;
$options = array_merge($this->Controller->request->params, $this->Controller->params['url'], $this->Controller->passedArgs
);
$vars = array('page', 'sort', 'direction', 'filter');
$keys = array_keys($options);
$count = count($keys);
for ($i = 0; $i < $count; $i++) {
if (!in_array($keys[$i], $vars) || !is_string($keys[$i])) {
unset($options[$keys[$i]]);
}
}
//save the options into the session
if ($options) {
if ($this->Session->check("Pagination.{$this->Controller->modelClass}.options")) {
$options = array_merge($this->Session->read("Pagination.{$this->Controller->modelClass}.options"), $options);
}
$this->Session->write("Pagination.{$this->Controller->modelClass}.options", $options);
$this->Session->write("region",$this->Controller->modelClass.'/'.$this->Controller->view);
}
$region=$this->Session->read('region');
//recall previous options
if ($this->Session->check("Pagination.{$this->Controller->modelClass}.options") && $region==$this->Controller->modelClass.'/'.$this->Controller->view ) {
$options = $this->Session->read("Pagination.{$this->Controller->modelClass}.options");
if(!isset($this->Controller->params['named' ]['page']) && isset($this->Controller->params['named' ]['direction']) )
{
$options['page']=1;
}
$this->Controller->passedArgs = array_merge($this->Controller->passedArgs, $options);
$this->Controller->request->params['named'] = $options;
}
}
}
Related
I want to render the view to a variable without directly sending it to the browser. I used to do it with cakephp2.*. But, I cannot figure out how to do it in CakePHP3. Could you please tell me how to do it?
ViewBuilder was introduced in CakePHP 3.1 and handles the rendering of views. When ever I want to render to a variable I always go look at how send email works.
From a controller:
function index() {
// you can have view variables.
$data = 'A view variable';
// create a builder (hint: new ViewBuilder() constructor works too)
$builder = $this->viewBuilder();
// configure as needed
$builder->layout('default');
$builder->template('index');
$builder->helpers(['Html']);
// create a view instance
$view = $builder->build(compact('data'));
// render to a variable
$output = $view->render();
}
For Ajax request/response, I use this:
public function print(){
if ($this->request->is('ajax')) {
$data = $this->request->getData();
$builder = $this->viewBuilder()
->setTemplatePath('ControllerName')
->setTemplate('print');
->setLayout('ajax');
->enableAutoLayout(false);
$view = $builder->build(compact('data'));
$html = $view->render();
$res = ['html' => $html];
$this->set('response',$res);
$this->set("_serialize",'response');
}
}
And the print.ctp is under Template/ControllerName
This document shows how to assign layout for error:
$this->layout = 'my_error';
http://book.cakephp.org/3.0/en/development/errors.html#exception-renderer
But i've 2 different layouts for frontend and backend. When NotFoundException is thrown, i want to assign different layout accordingly.
How can i do that? Please help me.
Just check appropriate criteria and set layout accordingly in your template. For e.g.
if ($this->request->param('prefix') === 'admin') {
$this->layout = 'admin';
} else {
$this->layout = 'default';
}
As #ADmad suggest.
Do check the request to identify frontend or backend inside error400.ctp file (you may have another error file, just is a example).
In my case use admin prefix, i did like this:
if (!empty($this->request->params['prefix']) && $this->request->params['prefix'] == 'admin') {
$this->layout = 'Admin/default';
$app = [
'App.imageBaseUrl' => 'admin/img/',
'App.cssBaseUrl' => 'admin/css/',
'App.jsBaseUrl' => 'admin/js/'];
Configure::write($app);
} else {
$this->layout = 'Frontend/default';
}
If you want to re-set layout you have to re-render the action, a redirect would be the most easy solution here. You could also use an AppView.
$view = new AppView();
$view->set($variables);
$view->render();
http://book.cakephp.org/3.0/en/views.html#the-app-view
I am adding an email validation step to my user registration. I cannot seem to get access to the arguments that are being passed in the emailed link;
link: activation.php?email=someone#somewhere.com&key=5614c46be05a95f55f2231d8dea41418d17b197a
Here is the page code;
class page_activation extends Page {
function init(){
parent::init();
if($this->api->auth->isLoggedIn())$this->api->redirect('index');
$loginAccount = $_GET['email'];
$activationKey = $_GET['key'];
$this->add('H1')->set('Activated');
$this->add('H3')->set('Account: '.$loginAccount);
$this->add('H3')->set('Key: '.$_GET['key']);
}
var_dump($_GET);
check network tab in inspector to make sure get params are actually passed.
check mod rewrite rules
I have found a solution in case it might help someone. It seems you can access the $_SERVER vars but the $_GET vars are lost by the time you get to the page. Here is some code that I used to accesses the passed vars from the email link;
class page_activation extends Page {
function init(){
parent::init();
if($this->api->auth->isLoggedIn())$this->api->redirect('index');
$data = parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY);
$queryParts = split('[;&]', $data);
$params = array();
foreach ($queryParts as $param) {
$item = explode('=', $param);
$params[$item[0]] = $item[1];
}
$loginAccount = $params['email'];
$activationKey = $params['key'];
$this->add('H1')->set('Activated');
$this->add('H3')->set('Account: '.$loginAccount);
$this->add('H3')->set('Key: '.$activationKey);
}
}
I am attempting to create a change password form in cakephp 2.0. I found a behavior that EuroMark created for 1.3 and now am having a tough time converting this code to work with 2.0. I know that it has something to do with the Auth Component as there were major changes to this component in 2.0.
public function validateCurrentPwd(Model $Model, $data) {
if (is_array($data)) {
$pwd = array_shift($data);
} else {
$pwd = $data;
}
$uid = null;
if ($Model->id) {
$uid = $Model->id;
} elseif (!empty($Model->data[$Model->alias]['id'])) {
$uid = $Model->data[$Model->alias]['id'];
} else {
return false;
}
if (class_exists('AuthExtComponent')) {
$this->Auth = new AuthExtComponent();
} elseif (class_exists($this->settings[$Model->alias]['auth'].'Component')) {
$auth = $this->settings[$Model->alias]['auth'].'Component';
$this->Auth = new $auth();
} else {
return true;
}
return $this->Auth->verifyUser($uid, $pwd);
}
I am getting an error on the line that reads $this->Auth = new $auth();
The error is as follows:
Argument 1 passed to Component::__construct() must be an instance of ComponentCollection, none given, called in C:\UniServer\www\new_company_test\app\Model\Behavior\change_password.php on line 117 and defined [CORE\Cake\Controller\Component.php, line 77]
and
Undefined variable: collection [CORE\Cake\Controller\Component.php, line 78]
it's also throwing this
Call to undefined method AuthComponent::verifyUser() in C:\UniServer\www\new_company_test\app\Model\Behavior\change_password.php on line 121
I am not sure if there is anything else that needs to be addressed in the script, I'm guessing not as there is no other place where Auth is used.
Any suggestions on what I need to do to get this to work? Any help is appreciated.
Thanks
you did discover that there is also a 2.0 branch, didnt you? :)
it should contain the same behavior:
https://github.com/dereuromark/tools/tree/2.0
either way, you need to pass a component collection into it:
$this->Auth = new AuthExtComponent(new ComponentCollection());
You should create a method verifyUser in your custom AuthExt Component which extends Auth Component for "current password" to work like so:
/**
* Quickfix
* TODO: improve - maybe use Authenticate
* #return bool $success
*/
public function verifyUser($id, $pwd) {
$options = array(
'conditions' => array('id'=>$id, 'password'=>$this->password($pwd)),
);
return $this->getModel()->find('first', $options);
$this->constructAuthenticate();
$this->request->data['User']['password'] = $pwd;
return $this->identify($this->request, $this->response);
}
/**
* returns the current User model
* #return object $User
*/
public function getModel() {
return ClassRegistry::init(CLASS_USER);
}
Maybe it is also possible to use the existing identify method in combination with a fake request object in the behavior directly?
I am thinking about using
$this->authenticate = array('Form'=>array('fields'=>array('username' => 'id')));
feel free to fork the behavior and submit a pull request.
"current password" is the only thing that is not yet cleanly solved right now.
I wanna have a different layout for the page not found 404 page. How can i set a different layout for that page?
Savant from the IRC helped me out and he suggest in using beforeRender(){} in the app_controller
// Before Render
function beforeRender() {
if($this->name == 'CakeError') {
//$this->layout = 'error';
}
}
CakeError is a catchAll for errors :D
In CakePHP 2.2.2 I changed the ExceptionRenderer in core.php with my own, like this:
app/Config/core.php:
Configure::write('Exception', array(
'handler' => 'ErrorHandler::handleException',
'renderer' => 'MyExceptionRenderer', // this is ExceptionRenderer by default
'log' => true
));
app/Lib/Error/MyExceptionRenderer.php:
App::uses('ExceptionRenderer', 'Error');
class MyExceptionRenderer extends ExceptionRenderer {
protected function _outputMessage($template) {
$this->controller->layout = 'error';
parent::_outputMessage($template);
}
}
Just you need to make layout changes in your error400.ctp file under /app/View/Errors/error400.ctp
Open that file and set layout by
<?php $this->layout=''; //set your layout here ?>
better to create an error.php file in your app folder
class AppError extends ErrorHandler {
function error404($params) {
$this->controller->layout = 'error';
parent::error404($params);
}
}
so you can avoid the if-testing at EVERY page render that savants' solution introduces
My solution for CakePHP 2.3
Change the ExceptionRenderer in core.php to use your own renderer.
app/Config/core.php:
Configure::write('Exception', array(
'handler' => 'ErrorHandler::handleException',
'renderer' => 'MyExceptionRenderer',
'log' => true
));
app/Lib/Error/MyExceptionRenderer.php:
App::uses('ExceptionRenderer', 'Error');
class MyExceptionRenderer extends ExceptionRenderer
{
/**
* Overrided, to always use a bare controller.
*
* #param Exception $exception The exception to get a controller for.
* #return Controller
*/
protected function _getController($exception) {
if (!$request = Router::getRequest(true)) {
$request = new CakeRequest();
}
$response = new CakeResponse(array('charset' => Configure::read('App.encoding')));
$controller = new Controller($request, $response);
$controller->viewPath = 'Errors';
$controller->layout = 'error';
return $controller;
}
}
The advantage to this approach is that it ensures any exceptions thrown from AppController don't cause an endless loop when rendering the exception. Forces a basic rendering of the exception message every time.
This simplest way I know of is to create this function in your AppController:
function appError($method, $messages)
{
}
You can then do whatever you want with the error, display it however you like, or not display it at all, send an email etc.. (I'm not sure if this method if still valid.)
There is also an option of creating app_error.php in your app root, with class AppError extends ErrorHandler in it, which enables you to override all kinds of errors. But I haven't done this yet, so I can't tell you more about it.
See cake/libs/error.php and cake/libs/object.php and of course The Book for more info.
Edit: Forgot to mention, once you caught the error, there's nothing preventing you to - for example - store the error in session, redirect to your "error handling controller", and then display it in your controller however you want.