Receive model data inside a CakePHP model callback function - cakephp

I try to use existing model data inside a models callback function in CakePHP 2.1 but can't get it working.
What I do is I try to get a users role in the beforeValidate() callback and check if it's empty. If yes, I'll set it. Normally I do it like this, and for the first creation of the record it works pretty well.
if (empty($this->data[$this->alias]['role']))
$this->data[$this->alias]['role'] = 'user';
The problem is, every time an existing record (user) gets updated, the role will be set again.
Question: So, how do I check if the field role is already set in the record data, not the post data (seems like $this->data[$this->alias] only contains POST data)?

There are three solutions to this problem as I can see it.
Set a default value in the database column (easiest, best?)
Add role to your inputs everytime.
Lookup the user and add a role if it's missing
The first two options seem obvious, so I'll just illustrate the last.
public function beforeValidate() {
if (!empty($this->id) {
$this->data[$this->alias][$this->primaryKey] = $this->id;
}
if (!empty($this->data[$this->alias][$this->primaryKey])) {
// user exists, check their data
$id = $this->data[$this->alias][$this->primaryKey];
$user = $this->find('first', array(
'conditions' => array($this->primaryKey => $id)
));
$this->data[$this->alias]['role'] = $user[$this->alias]['role'];
}
if (empty($this->data[$this->alias]['role'])) {
// new user but missing a role
$this->data[$this->alias]['role'] = 'user';
}
return true;
}
This callback will check if an ID was passed, and if so it will look up the user and populate the role field. Then, it checks for an empty role and fills it with the default if necessary. Obviously he more code you have, the more possibilities for bugs, so I suggest the first option for default column values.

try:
if (empty($this->data[$this->alias]['role']) && empty($this->role)) {
$this->data[$this->alias]['role'] = 'user';
}

Related

Creating muliple users in a loop from array

How does one go about creating multiple new users, say in a loop, from an array in a different controller?
I have an issue where attempting to create multiple users in a form submit fails, but creating a single new user works as designed. It appears the issue may be when saving the new user and then bringing the new user_id back in the return statement. Although the new id comes back, subsequent users (2nd, 3rd, etc) all get the same id value, and it appears that the subsequent $this->save calls modify the first created user rather than create add'l ones. Any none of the new users appear in the database. (again, the problem only happens when more than one new users will be created.)
My one small clue is that if I var_dump($user) in my importPublicLDAPUser() function (user.php) just after the $user = $this->save(array('User' => array( ... ))); then for the first element I see both 'modified' and 'created', whereas I see only 'modified' for the rest. This leads me to believe there's a step missing, like the user needs to be saved or commit (??) before the next user can be be created.
I tried changing this to $user = $this->User->save(array('group_id' => 3, ... and adding a 'create' before: $this->User->create(); but these produce errors 'Call to a member function save() on a non-object' and 'Call to a member function create() on a non-object'.
My application manages documents. Each document can have many authors, so it has controllers for: docs, doc-types, users, groups, and authors.
When a new document is entered, the form allows selection of multiple users to create 'Author' records. In addition to the local users table, it also searches our LDAP server (both via auto-sugggest) and also allows input into a text field. So, Authors are selected from
the existing table of users
via the LDAP helper
free text entry.
This result is two arrays: $Authors (from local users tables), and $badAuthors (LDAP and text-input) which the app then tries to add to the local users table when the form is submitted.
The form works just fine if:
one or more authors are added from local users table;
a single author is added from LDAP (succeeds in creating a new entry in users table), and zero or more local users
a single author is added from text input (also succeeds), and zero or more local users
However if two or more non-local users are added ($badAuthors has more than one element) then the form fails. "fails" means that either the Author or User save routine failed, and so it skips the Document commit $this->Docu->commit(); and I spit out an error via ajaxResponse. Thus, the app works as designed, but only with one new User entry at a time, even though the form is designed to allow Authors/badAuthors to be >1.
What I don't understand is why when I loop through bad authors why it doesn't correctly add the users if $badAuthors has more than one element.
As the user enters each name (which is checked against the users table and LDAP via ajax helpers, etc) and then selected, an author_name_list array is built. And then:
foreach($this->params['form']['author_name_list'] as $author_name){
$user_id = $this->Docu->User->field('id',array('User.name' => $author_name));
if($user_id){
$Authors['Author'][]=array(
'docu_id'=>$this->Docu->id
,'user_id'=>$user_id
);
}else{
$badAuthors[] = array('name'=>$author_name);
}
}
So $badAuthors is now those found in LDAP or entered manually.
So now I try to create/save all badAuthors...
docu controller (docu_controller.php):
if(count($badAuthors)){
foreach($badAuthors as $key => $author){
$this->Memo->User->create(); // ** <-- this was missing!! **
if($ldap_author = $this->Docu->User->importPublicLDAPUser($author['name'])){
unset($badAuthors[$key]);
$Authors['Author'] []= array(
'docu_id'=>$this->Docu->id
,'user_id'=>$ldap_author['User']['id']
,'precedence' => $author['precedence']
);
} elseif ($new_author = $this->Docu->User->newReadonlyUser($author['name'])) {
unset($badAuthors[$key]);
$Authors['Author'] []= array(
'docu_id'=>$this->Docu->id
,'user_id'=>$new_author['User']['id']
,'precedence' => $author['precedence']
);
}
}
}
if(!count($badAuthors)){
$authors_saved = true;
foreach($Authors['Author'] as $author_arr){
$this->Docu->Author->create();
if(!$this->Docu->Author->save(array('Author' => $author_arr))){
$authors_saved = false;
break;
}
}
}
user model (user.php)
function afterSave($created) {
if (!$created) {
$parent = $this->parentNode();
$parent = $this->node($parent);
$node = $this->node();
$aro = $node[0];
$aro['Aro']['parent_id'] = $parent[0]['Aro']['id'];
$this->Aro->save($aro);
}
}
function importPublicLDAPUser($cn){
App::import('Vendor','adLDAP',array('file'=>'adLDAP.php'));
$oLDAP = new adLDAP(Configure::read('LDAP_options.email'));
$oLDAP->authenticate(NULL, NULL);
$filter = '(&(cn='.$oLDAP->ldap_escape($cn).'))';
$ldap_res = #$oLDAP->search($filter, array('cn', 'uid','profitcenter'),1);
if(isset($ldap_res['count']) && ($ldap_res['count'] > 0)){//found it
$user = $this->save(array('User' => array(
'group_id' => 3,
'name' => $ldap_res[0]['cn'][0],
'username' => $ldap_res[0]['uid'][0],
'grpnum' => pc2grpnum($ldap_res[0]['profitcenter'][0])
)));
if($user){
$user['User']['id'] = $this->id;
}
return ($user ? $user : false);
}else{
return false;
}
}
Any suggestions? Thanks!!
It turns out that in my docu_controller.php I was missing a create() call. It seems that without a create, an object can still be saved/created when the other controller does a commit(). So before adding the create(), prior to the commit, in later loop iterations I was still modifying the original object, not any new ones. By adding a create in the controller, the save in the method function acts on the new user for each loop iteration.
in controller:
if(count($badAuthors)){
foreach($badAuthors as $key => $author){
$this->Memo->User->create();
if($ldap_author = $this->Memo->User->importPublicLDAPUser($author['name'])){
in method:
function importPublicLDAPUser($cn){
....
$user = $this->save(array('User' => array(...

CakePHP User Lookup based on their ID

I thought this would be a relatively common thing to do, but I can't find examples anywhere, and the Cookbook's section on find() was not clear in the slightest on the subject. Maybe it's just something that's so simple Cake assumes you can just do it on your own.
All I'm looking to do here is retrieve a User's name (not the currently logged-in user…a different one) in Cake based on their ID passed to my by an array in the view.
Here's what I've got in the controller:
public function user_lookup($userID){
$this->User->flatten = false;
$this->User->recursive = 1;
$user = $this->User->find('first', array('conditions' => $userID));
//what now?
}
At this point, I don't even know if I'm on the right track…I assume this will return an array with the User's data, but how do I handle those results? How do I know what the array's gonna look like? Do I just return($cakeArray['first'].' '.$cakeArray['last'])? I dunno…
Help?
You need to use set to take the returned data, and make it accessible as a variable in your views. set is the main way you send data from your controller to your view.
public function user_lookup($userID){
$this->User->flatten = false;
$this->User->recursive = 1;
// added - minor improvement
if(!$this->User->exists($userID)) {
$this->redirect(array('action'=>'some_place'));
// the requested user doesn't exist; redirect or throw a 404 etc.
}
// we use $this->set() to store the data returned.
// It will be accessible in your view in a variable called `user`
// (or what ever you pass as the first parameter)
$this->set('user', $this->User->find('first', array('conditions' => $userID)));
}
// user_lookup.ctp - output the `user`
<?php echo $user['User']['username']; // eg ?>
<?php debug($user); // see what's acutally been returned ?>
more in the manual (this is fundamental cake stuff so might be worth having a good read)

Is it safe to do query with Model while in Behavior's BeforeSave callback?

I don't see this documented anywhere, so I ask you, my dear Cake-eaters.
Inside a CakePHP's Behavior::BeforeSave(&$Model) method, I read and write changes to $Model->data array. Before I am finished, I need to read some other records from the database. I am worried that, if I use $Model->find(), it will overwrite the current data within the model, which is about to be saved.
Viewing the source code, the Model::find() function clearly resets the Model::$id variable. This is the same variable I later use to check if a field is being updated.
Here's an example:
<?php
class UniqueBehavior extends ModelBehavior {
function beforeSave(&$Model){
$value = $Model->data[$Model->alias]['unique_field'];
$query = array('conditions' => array('unique_field' => $value));
if ($Model->find('first', $query){
// Does $Model::find() reset the internal $Model->data array?
$Model->data[$Model->alias]['unique_field'] = "..."
//... some other code here
}
//ALSO...
if ($Model->exists()) // Returns true if a record with the currently set ID exists.
$slug = $Model->field('slug');
// this should fetch the slug of the currently updated Model::id from the database
// if I do find()'s, can I trust that the record I'm getting is the right one?
}
}
?>
you can always store the current id in $tmp and assign this stored id back to the model after you are finished
$tmp = $Model->id;
// ...
$Model->id = $tmp;
This way you don't run into problems using the Model-id.
If it is save or not depends on how you work in your model.
I - for example - never rely on this id. I always assign the id to the model manually prior to any update or delete call etc. But this is not necessary, of course. You have to be more careful then, though.

CakePHP: User data saveAll silently fails?

I have a User model which hasMany Instrument and Genre. The Instrument and Genre save when I use the code below:
$this->User->saveAll($this->data, array(
'fieldList' => array('User', 'UserInstrument', 'Genre')))
)
But the User doesn't. All of the invalidFields arrays are empty in my debugger (User, UserInstrument, Genre, UserGenre, Instrument).
One weird thing I'm noticing, though, is that in here:
public function beforeSave() {
// get the password reset
if(isset($this->data[$this->alias]['password_reset'])) {
$this->data[$this->alias]['password'] = $this->data[$this->alias]['password_reset'];
unset($this->data[$this->alias]['password_reset']);
}
// get rid of the password confirm
if(isset($this->data[$this->alias]['password_confirm'])) {
unset($this->data[$this->alias]['password_confirm']);
}
// hash the password
if (isset($this->data[$this->alias]['password'])) {
$this->data[$this->alias]['password'] = AuthComponent::password($this->data[$this->alias]['password']);
}
return true;
}
I am unsetting the password_reset and password_confirm, but after the save is done, these fields magically appear back in $this->data['User'] (probably re-grabbed from $_POST). But if there was an error saving then the saveAll would return false. I have nothing in my error log either.
Any ideas as to why this is silently failing? Thanks!
if your purpose is to use hashing for newly created users, please consult this
Especially the beforeSave function which is simply just
public function beforeSave() {
if (isset($this->data[$this->alias]['password'])) {
$this->data[$this->alias]['password'] = AuthComponent::password($this->data[$this->alias]['password']);
}
return true;
}
Please understand that in cakephp, if you include a field that is actually NOT in the datatable for the models, that field would be ignored. So you need not actually do an unset for your password_reset and password_confirm fields.
As for saving associated records and using fieldList, I noticed that you did not explicitly state the fields that you want to save in the fieldList key of the array.
Moreover, you stated that User hasMany Instrument and User hasMany Genre.
Please use the saveAssociated way.
Prepare your data like this in the controller:
$this->data['Instrument'] = array(
array('instrument_field1'=>'v1',
'instrument_field2' => 'v2',
),// first instrument
array('instrument_field1' => 'v1',
'instrument_field2' => 'v2')// second instrument
);
$this->data['Genre'] = array(
array('field1'=>'v1',
'field2' => 'v2',
),// first genre
array('field1' => 'v1',
'field2' => 'v2')// second genre
);
OR in your forms you do something like this:
$this->Form->input('Instrument.0.field1'); // for the first instrument
$this->Form->input('Instrument.1.field1'); // for the second instrument
Comment in my answer if I misunderstood the question.
I took out fieldList. Not the safest solution, but this was just causing more of a hassle than it was worth.

CakePHP user session not updating but database yes

I'm developing with cakePhP and I have the following problem:
When a user logs in with his name and password to the account system that I've created, he can save items (images) as favorites. This is saved in a text field into the database. What is saved is the image ID.
The saving process works perfectly, the user clicks on the images and they're added to that field (it actually saves all the IDs as a text array that I process later).
The problem comes when removing images. When the user does it (I'll post the code below), the images is removed correctly from the database (I go to PHP MyAdmin and I see it). This means that the array that holds the favorite images IDs is updated instantly. However, when I reload that array from the website, it hasn't been updated. It's like it's stored in the caché or something. Then, if the user logs out and logs in again, then he can see the correct one. The thing is that I have other things in my website that work in a similar way and they all get updated instantly, so I can't see why this doesn't.
This is the code that I use to remove the ID from the database:
function remove_favorite($pictureID) {
$this->User->id = $this->Auth->User('id'); //We get the ID of the current user
$favoritesArray = $this->User->deleteFavoritePicture($this->User->id, $pictureID); //This function retrieves the array (string) of pictures from the user's table, and deletes all the images with the ID passed as parameter, returning the updated array (string)
$fields = array('images_favorites' => $favoritesArray, 'modified' => true); //We indicate the field that we're going to update in the users table
//We save the new string that doesn't contain the deleted image anymore
if($this->User->save($fields, false, array('images_favorites'))) {
$this->Session->setFlash(__('The image has been removed from your favorites', true));
} else {
$this->Session->setFlash(__('Error removing image from favorites, please try again', true));
}
$this->redirect(array('action' => 'manage_favorites',$this->User->id));
}
This is how the deleteFavoritePicture function looks like:
function deleteFavoritePicture($userID, $pictureID) {
$userInfo = $this->find("id = $userID");
$favoritePicturesString = $userInfo['User']['images_favorites'];
$favoritePicturesArray = explode(",", $favoritePicturesString); //Array
$i = 0;
while ($i < count($favoritePicturesArray)) {
//We remove from the array the images which ID is the one we receive to delete
if ($favoritePicturesArray[$i] == $pictureID) unset($favoritePicturesArray[$i]);
$i++;
}
$favoritePicturesString = implode(",", $favoritePicturesArray); //String
return ($favoritePicturesString);
}
That's it. Does anyone now what can be going on? Thanks so much in advance for any clue!
EDIT
Ok, I think I found something that may give a clue of what's going on here:
This is the code for the manage_favorites action:
function manage_favorites($id) {
//$user = $this->User->find("id = $id");
$user = $this->Auth->user();
$this->set('user', $user);
}
That is the action that is called for the page when a user wants to modify his favorites. The same action is called once the user removes a favorite. Here's the thing:
If I use the $id parameter in the manage_favorites function and the $user = $this->User->find("id = $id"); line (the one quoted now), then the problem does not exist! This is how I used to have it. HOWEVER, I had to change it because it was a big security flaw, since the user id ($id) was a visible parameter who anyone could change, and then access other users accounts. What I did was changing the way I obtain the user array of favorite images, using the following line: $user = $this->Auth->user();. This is how I have it now (well, and also without the $id parameter in the function header), so the user information (including the favorites array) comes from the Auth component, instead directly from the database.
So, the problem is clear: when the user deletes a favorite, it's doing it on the array in the database. WHen I show the result of that operation, the array I'm retrieving is not the one in the DB, it's the one in the session. That's why it's not showing the changes.
How can I avoid this without using a non-secure method like the one I had before?
When you save, the array passed to the save method of the model should look like this:
[User] => array(
[field] => value,
[field2] => value2,
...
)
In your example, you clearly haven't added the [User] key.
Also, is your modified field actually the default Cake modified field? That is, the DATETIME field which changes to the current time when the row is updated?
Lastly, maybe you have debugging set to 2 in config.php. try changing this to 0 (as in production) and see if caching persists.
Hope some of the points I have mentioned above will solve your problem. Please let me know!
There could be two things wrong with this.
What does your deleteFavoritePicture method look like? There could be something being done wrong there.
You're passing false as the second parameter to the User::save method, which means that you don't want to validate. Unless there is a SQL error, then this will return true even if it doesn't validate properly, I believe. Try changing this false to true and see if your results differ.

Resources