How to get the model and action name of the calling action - cakephp

I have a log feature in my project that tracks all changes to a couple of different models. Say, whenever a user or an admin edits an account. Or when a user creates a new record. All those actions get recorded in the log table.
I would like to have the log model retrieve the model name and action name by itself in a beforeSave action when called to save a new record. I call it now from a controller action making the save like this:
$this->Log->save(array(
'user'=>$this->Auth->user('id'),
'model'=>$this->name,
'action'=>$this->action));
I would love to be able to shorten it down to:
$this->Log->save();

I found alkerman's wonderful LogableBehavior and it is working like a charm. No need to reinvent the wheel.

I think I'd be inclined to either write a method on app_controller such as
saveLog()
which calls
$this->Log->save($uma_array)
or simplify your existing construct by doing
$this->Log->save($this)
and untangling the data inside the method.
EDIT:
Of course, the best way to do this would be to use observable behaviour using teknoid's pattern: http://nuts-and-bolts-of-cakephp.com/2009/08/10/observer-pattern-the-cakephp-way/
It's not that difficult to implement.

Related

Access Auth component in Model

I want to create a Behavior to automagically populate a 'created_by' field when a record gets saved with the id of the logged in user, much like timestamp does with 'created'. However, apparently you can get $this->Auth->user('id') everywhere except in the Model.
I am having to resort to updating the request data in the controller's add() method before the save() call, which works but it's not DRY. I'd like to have bake add the behavior in a model's table class for any entity that has a 'created_by' field.
Any ideas how I could do this in a Behavior, the way Cake intended?
A possibility is to use the Events System. I personaly did this by using a Component and a Behavior that communicate through the Model.beforeSave event. Basically the component is responsible to add a new listener that passes a function to the behavior allowing to retrieve the authenticated user id.
You can have a look at the code of both classes here: UserLinkComponent, UserLinkBehavior
and to use them:
Controller:
$this->loadComponent('Alaxos.UserLink');
Model\Table:
$this->addBehavior('Alaxos.UserLink');
The Blame behavior does what you need. You can either use it or study it's code to implement your own.

What is the cakePHP way of checking if a user is allowed to perform an action on a particular item?

Working with cakePHP this is my situation:
I have Users and Orders. Orders are created by Users. Only the user that created the Order is allowed to edit it. (admins can also, but I don't think that is important).
I am using the standard Auth component and have an isAuthorized function in my OrdersController that checks if the user is logged in and stops users from performing actions that they are not allowed to perform.
I want to make a decision on whether or not the user can perform the action based on the params passed and the data that comes out of the database. i.e. does the user own the order they are trying to edit? I am currently checking inside each action if this is the case.
Is there a way that I can trigger the same workflow that is triggered by returning false from isAuthorized? maybe throwing an Exception?
I don't want to do these finer checks inside the isAuthorized function, because it will require ugly methods of accessing the passed params, and duplication of data retrieval. How does cakePHP expect me to handle this?
(I have more complicated checks to make in other controllers)
This is what you're looking for:
http://book.cakephp.org/2.0/en/tutorials-and-examples/blog-auth-example/auth.html#authorization-who-s-allowed-to-access-what
overriding the AppController’s isAuthorized() call and internally
checking if the parent class is already authorizing the user. If he isn’t, then just allow him to access the add action, and conditionally access
edit and delete.
Hope this helps
There are a few ways to get this to work. I have a simple example outlined here:
http://nuts-and-bolts-of-cakephp.com/2009/04/22/simplistic-example-of-row-level-access-control-with-auth-security-and-app-model-in-cakephp/
It should give you an idea of how to handle this in general, and then you can build on top of that as one approach.

CakePHP - Access session data in Model::afterSave()

I have MyModel that, whenever it is created or updated, I want to insert a row into my_model_changes (MyModelChange) that is a log of all changes.
The problem is that I need MyModelChange to be able to save the current user's ID, so that it stores who authorised the change to MyModel.
I know the model shouldn't be able to access the Auth session in MVC. However, every change to MyModel must be logged, and so I don't want to leave it up to people to remember to do this in a controller every time they modify the model, because there is a risk it will be forgotten or not done properly, in which case the log won't be complete. Instead, I want to use MyModel::afterSave() so that it is automated and will always happen properly.
So how do I get MyModel to find the Auth user's ID?
Maybe my solution is not the most elegant way but I used it in my app and it worked. Before saving in the controller I extend $this->data with the user_id like this:
$this->data['MyModel']['user_id']=$this->Auth->user('id');
so I can use it in my model afterSave method. I hope it could help.
Is it not possible to do what you're wanting to do in the app_controller.php's beforeFilter? That way you don't have to set anything in the other controllers, except the parent::beforeFilter() ofcourse if you override it.
But if it's really needed, you can put this in your Model to access session data:
App::import('Component', 'SessionComponent');
$Session = new SessionComponent();

CakePHP afterSave Timing

I have a situation where, in a model's afterSave callback, I'm trying to access data from a distant association (it's a legacy data model with a very wonky association linkage). What I'm finding is that within the callback I can execute a find call on the model, but if I exit right then, the record is never inserted into the database. The lack of a record means that I can't execute a find on the related model using data that was just inserted into the current.
I haven't found any mention of when data is actually committed with respect to when the afterSave callback is engaged. I'm working with legacy code, but I see no indication that we're specifically engaging transactions, so I'm trying to figure out what my options might be.
Thanks.
UPDATE
The gist of the scenario is this: We're taking event registrations, but folks can be wait listed. A user can register (or be registered) for a given Date. After a registration is complete, I need to check the wait list for the existence of a record for the registering user (WaitList.user_id) on the date being registered for (WaitList.date_id). If such a record exists, it can be deleted because it's become an active registration.
The legacy schema puts me in a place where the registration isn't directly tied to a date so I can't get the Date.id easily. Instead, Registration->Registrant->Ticket->Date. Unintuitive, I know, but it is what it is for now. Even better (sarcasm included), we have a view named attendees that rolls all of this info up and from which I would be able to use the newly created Registration->id to return Attendee.date_id. Since the record doesn't exist, it's not available in the view.
Hopefully that provides a little more context.
What's the purpose of the find query inside of your afterSave?
Update
Is it at all possible to properly associate the records? Or are we talking about way too much refactoring for it to be worth it? You could move the check to the controller if it's not possible to modify the associations between the records.
Something like (in psuedo code)
if (save->isSuccessful) {
if (onWaitList) {
// delete record
}
}
It's not best practice, but it will get you around your issue.

CakePHP: Updating a session variable after save

I have a User object that, upon successful authentication, is tucked into the session (sans security info) for easy recall and for determining whether we have an authenticated user or anonymous session. There are several paths by which the user can alter some or all of his or her information and I'd like to keep that session value up to date. The obvious answer is to update the value in the afterSave() callback, but that, of course, violates MVC.
Is there another way of capturing every change in one place so that I don't have to drop session writes all over the place? I can't think of anything, nor have I been able to find any other ideas. Am I the only person trying to do something like this?
Thanks.
Final Solution: I marked neilcrookes' response as the answer, frankly, because there doesn't seem to be the better way. Since this way violates my OCD senses, though, I took a slightly different path. I decided to have my User::authenticate() method return the authenticated user object to the caller so it can do whatever it wants with it. One of the things that the callers "want" to do is to drop that value in the session. It's redundancy, but it's very, very limited. In my mind, that felt better than accessing the session from the model (though it's certainly a damned if you do, damned if you don't scenario).
//in users controller
if ($this->User->save()) {
$this->Auth->login($this->User->read());
$this->Session->setFlash[.. etc]
And for the record, I do not agree with the answer of neilcrooks, but I will refrain from feeding the troll.
Some might disagree but I'd screw MVC, do it in Model::afterSave() and use $_SESSION - test for the session before writing to it, in case it's not started for example you are saving against the model in a shell or something.
MVC is a general pattern - a guideline, you can bang your head against it trying to figure out how to achieve something that doesn't quite fit, or just do it another way and move onto to something more important.
Bring on the flames.
after save
Use Like this
$this->Session->write('Auth.User.mmid', $kinde['Kindle']['id']);
You should be able to just use AppController to create the necessary callback(s) that keep your session data up to date. So, for instance, you could have your User model afterSave() set a property called changed to true. Then in your AppController->afterFilter() you check that property and update the session data as necessary.
Alternatively, you could write a component through which to update your user info and also your session data. Then any controller that needs to change user info just needs to include that component.
There's no need to write redundant code or break MVC.

Resources