How can i deal with correct base url with wrong parameter?
Cakephp if u pass wrong controller or action name, it will lead to 404 page.
But if i enter correct controller/action name with wrong parameter, how can i deal with this kind of case.
Example:
correct: http://localhost/controller/action?params=123
wrong: http://localhost/controller/action?par23=123
par23 is wrong name. in this case i want to redirect to some pages like 404 page. is there any method or configuration i do not need to check parameter in controller and redirect to some page everytime.
thank you
You will need to check this manually in your controller.
Instead of looking for any extra wrong param, I would simply check if your needed params are there. If not, you can throw an exception.
This is how Cake's baked code does it:
// MyModelController.php
public function view($id = null) {
$this->MyModel->id = $id;
if (!$this->MyModel->exists()) {
throw new NotFoundException(__('Invalid thing'));
}
// ...
}
So here, an ID is needed. If it is not given (default value null) or no record with this Id exists, an Exception that results in a 404 is thrown.
Note that this has an extra benefit: It is not only covering the case where a param is missing, it also correctly returns an error if the param is present but references a non-existent item by $id.
Related
Currently, I'm using the CRUD v4 plugin for Cakephp 3. For the edit function in my user controller it is important that only a user itself can alter his or her credentials. I want to make this possible by inserting the user id from the authentication component. The following controller method:
public function edit($id = null){
$this->Crud->on('beforeSave', function(\Cake\Event\Event $event) {
$event->subject()->entity->id = $this->Auth->user('id');
});
return $this->Crud->execute();
}
How can I make sure I don't need to give the id through the url? The standard implementation requires the url give like this: http://domain.com/api/users/edit/1.json through PUT request. What I want to do is that a user can just fill in http://domain.com/api/users/edit.json and send a JSON body with it.
I already tried several things under which:
$id = null when the parameter is given, like in the example above. Without giving any id in the url this will throw a 404 error which is caused by the _notFound method in the FindMethodTrait.php
Use beforeFind instead of beforeSave. This doesn't work either since this isn't the appropriate method for the edit function.
Give just a random id which doesn't exist in the database. This will through a 404 error. I think this is the most significant sign (combined with point 1) that there is something wrong. Since I try to overwrite this value, the CRUD plugin doesn't allow me to do that in a way that my inserting value is just totally ignored (overwriting the $event->subject()->entity->id).
Try to access the method with PUT through http://domain.com/api/users.json. This will try to route the action to the index method.
Just a few checks: the controllerTrait is used in my AppController and the crud edit function is not disabled.
Does anyone know what I'm doing wrong here? Is this a bug?
I personally would use the controller authorize in the Auth component to prevent anyone from updating someone else's information. That way you do not have to change up the crud code. Something like this...
Add this line to config of the Auth component (which is probably in your AppController):
'authorize' => ['Controller']
Then, inside the app controller create a function called isAuthorized:
public function isAuthorized($user) {
return true;
}
Then, inside your UsersController you can override the isAuthorized function:
public function isAuthorized($user) {
// The owner of an article can edit and delete it
if (in_array($this->request->action, ['edit'])) {
$userId = (int)$this->request->params['pass'][0];
if ($user['id'] !== $userId) {
return false;
}
}
return parent::isAuthorized($user);
}
I have an API wirtten and gave out instructions how to us it.
For example to test the login you can call /api/login
Now I see in the logs, that someone keeps calling /API/LOGIN and gets an error 500.
Is there somehow a way, to catch such errors ONLY when calling /api/ controller functions?
In that case, I would like to send back a response like Error. Wrong function call.
I do not want to send this in general when a error 500 happens. Really only when /api/ related.
The error which gets reported belongs to the fact that I am calling in AppController::beforeFilter() a function like
$this->Api->check($username)
And I get this error when debug=2
Call to a member function check() on a non-object
When I call /api/login/ the functions works perfect.
I look forward for any tips! Thanks!
The problem you are facing isn't the casing of the action (in PHP method names are case insensitive), but the casing of the controller. It won't find APIController and therefore throw an missing controller exception. Your AppController is then being invoked as it is being extended by CakeErrorController which is used on errors.
I can only assume that $this->Api refers to a model, and since the actual controller is CakeErrorController, that model of course isn't being loaded, hence the non-object error.
There are various ways to solve this problem, personally I'd probably hook in to the Dispatcher.beforeDispatch event and throw an exception or define an appropriate response if necessary, something like this:
// app/Config/bootstrap.php
App::uses('CakeEvent', 'Event');
App::uses('CakeEventManager', 'Event');
CakeEventManager::instance()->attach(
function(CakeEvent $event) {
$controller = $event->data['request']->params['controller'];
$action = $event->data['request']->params['action'];
if(strtolower($controller) === 'api') {
$response = $event->data['response'];
if($controller !== 'api') {
$response->statusCode('403');
$response->body('Invalid controller message');
return $response;
}
if(strtolower($action) !== $action) {
$response->statusCode('403');
$response->body('Invalid action method');
return $response;
}
}
},
'Dispatcher.beforeDispatch',
array('priority' => 11)
);
This would enforce using lowercase for the controller and the action in case the api controller is being targeted.
However as already mentioned, method names are case insensitive, so forcing lowercase for actions isn't necessary from a technical point of view. Anyways, it's just an example...
I am trying to retrieve the id of the current user logged in so i can save it to another table. I've been around stack overflow and found a few ways of doing this but nothing seems to work.
This throws the error: Call to a member function user() on a non-object
$this->Auth->user('id');
This returns a null value:
$this->Session->read('User.id');
I played around with this for a bit https://github.com/mcurry/cakephp_static_user but for some reason it complained that I was calling unregistered functions when I know I had implemented them.
I've tried a few variations of the auth and session lines but they all return null or throw an error. Can anyone shed some light on what I might be missing or doing wrong?
Try this:
$loggedInUser = AuthComponent::user();
This gives the whole user array, if you're just after the id then you need this:
$userId = $loggedInUser['User']['id'];
It's what I'm using where I need it, and it works well for me.
It seems like you are not using CakePHP's built-in Auth component.
Your code says:
$components = array('DebugKit.Toolbar', 'Session', 'DataTable', 'RequestHandler', 'Usermgmt.UserAuth')
To include CakePHP's Auth component:
$components = array('...', 'Auth')
I want to serve JSONP content with CakePHP and was wondering what's the proper way of doing it so.
Currently I'm able to serve JSON content automatically by following this CakePHP guide.
Ok, I found a solution on this site. Basically you override the afterFilter method with:
public function afterFilter() {
parent::afterFilter();
if (empty($this->request->query['callback']) || $this->response->type() != 'application/json') {
return;
}
// jsonp response
App::uses('Sanitize', 'Utility');
$callbackFuncName = Sanitize::clean($this->request->query['callback']);
$out = $this->response->body();
$out = sprintf("%s(%s)", $callbackFuncName, $out);
$this->response->body($out);
}
I hope it helps someone else as well.
I've as yet not found a complete example of how to correctly return JSONP using CakePHP 2, so I'm going to write it down. OP asks for the correct way, but his answer doesn't use the native options available now in 2.4. For 2.4+, this is the correct method, straight from their documentation:
Set up your views to accept/use JSON (documentation):
Add Router::parseExtensions('json'); to your routes.php config file. This tells Cake to accept .json URI extensions
Add RequestHandler to the list of components in the controller you're going to be using
Cake gets smart here, and now offers you different views for normal requests and JSON/XML etc. requests, allowing you flexibility in how to return those results, if needed. You should now be able to access an action in your controller by:
using the URI /controller/action (which would use the view in /view/controller/action.ctp), OR
using the URI /controller/action.json (which would use the view in /view/controller/json/action.ctp)
If you don't want to define those views i.e. you don't need to do any further processing, and the response is ready to go, you can tell CakePHP to ignore the views and return the data immediately using _serialize. Using _serialize will tell Cake to format your response in the correct format (XML, JSON etc.), set the headers and return it as needed without you needing to do anything else (documentation). To take advantage of this magic:
Set the variables you want to return as you would a view variable i.e. $this->set('post', $post);
Tell Cake to serialize it into XML, JSON etc. by calling $this->set('_serialize', array('posts'));, where the parameter is the view variable you just set in the previous line
And that's it. All headers and responses will be taken over by Cake. This just leaves the JSONP to get working (documentation):
Tell Cake to consider the request a JSONP request by setting $this->set('_jsonp', true);, and Cake will go find the callback function name parameter, and format the response to work with that callback function name. Literally, setting that one parameter does all the work for you.
So, assuming you've set up Cake to accept .json requests, this is what your typical action could look like to work with JSONP:
public function getTheFirstPost()
$post = $this->Post->find('first');
$this->set(array(
'post' => $post, <-- Set the post in the view
'_serialize' => array('post'), <-- Tell cake to use that post
'_jsonp' => true <-- And wrap it in the callback function
)
);
And the JS:
$.ajax({
url: "/controller/get-the-first-post.json",
context: document.body,
dataType: 'jsonp'
}).done(function (data) {
console.log(data);
});
For CakePHP 2.4 and above, you can do this instead.
http://book.cakephp.org/2.0/en/views/json-and-xml-views.html#jsonp-response
So you can simply write:
$this->set('_jsonp', true);
in the relevant action.
Or you can simply write:
/**
*
* beforeRender method
*
* #return void
*/
public function beforeRender() {
parent::beforeRender();
$this->set('_jsonp', true);
}
I've been using routing with "slug" as a named parameter, for example:
Router::connect('/category/:slug', array('controller'=>'categories', 'action'=>'view'), array('pass'=>array('slug'), 'slug'=>'[a-z0-9\-]+'));
I've now stumbled across a problem because I want to restrict the above route to logged in users only, so I've put this in the beforeFilter() function of my CategoriesController:
if(!$this->Auth->loggedIn()) {
$this->Auth->deny('view');
}
Now if I go to /category/my-category (while logged out) I'll be redirected to my application's login page, unfortunately after I log in I'm redirected to /categories/view/my-category/slug:my-category
This is due to line 317 of AuthComponent.php, where we have:
$this->Session->write('Auth.redirect', Router::reverse($request));
So it seems when I do Router::reverse($request) on the above route it doesn't work properly (because it thinks "my-category" should be both a passed and a named parameter).
Is this a problem with the way I've set up this route, or is it a bug with CakePHP? Surely Router::reverse($request) should always return the URL we're currently at?
Any advice appreciated...
I'm not 100% sure if it is a bug or not, but until we find out a work-around could be to manually set the new loginRedirect in your category controller like so:
if(!$this->Auth->loggedIn()) {
$this->Auth->deny('view');
$this->Auth->loginRedirect = '/categories/' . $this->request->params['slug'];
}
Note, check that $this->request->params['slug'] is the right var to use, not 100% off the top of my head.