How to report errors on non-column input fields - cakephp

I have a form for creating a batch of events. It's got inputs for name, description, location, start and end time, all of which are columns in the events table. It's also got a number of inputs for dates, using field names like dates.0.date, dates.1.date, etc.
echo $this->Form->input('name');
echo $this->Form->input('description');
echo $this->Form->input('location');
echo $this->Form->input('start');
echo $this->Form->input('end');
for ($i = 0; $i < $this->request->data['repeat_count']; ++ $i) {
echo $this->Form->input("dates.$i.date", ['type' => 'date']);
}
The save process loops through the dates array in the submitted input, creating an entity with the rest of the data and the date for each one, and saving it. That is, the resulting entities will have fields called name, description, location, start, end and date. (Hope that's clear!) This part works great, and it's wrapped in a transactional function so that the events are only saved if all succeed.
$event = $this->Events->newEntity(array_merge(
$this->request->data, ['team_id' => $id, 'dates' => []]));
if (!$this->Events->connection()->transactional(function () use ($id, $event) {
for ($i = 0; $i < $this->request->data['repeat_count']; ++ $i) {
// Use $e for the entity that is actually saved, so the
// $event entity isn't mangled
$e = $this->Events->newEntity(array_merge(
$this->request->data,
[
'team_id' => $id,
'date' => $this->request->data['dates'][$i]['date']
]
));
$this->Events->save($e);
}
if ($event->errors()) {
$this->Flash->warning(__('The event could not be saved. Please correct the errors below and try again.'));
return false;
}
return false;
})) {
$this->set(compact('event'));
return;
}
The problem is when an invalid date is provided. The entity is not saved, and the error is correctly located in $e->errors('date'). But I can't figure out how to report that error back to the user on a date-by-date basis. Extrapolating from the entity documentation and using the dot notation that is standard elsewhere, I've tried $event->errors("dates.$i.date", $e->errors('date'));, and confirmed that any such errors are preserved in the list of errors in the entity when the view is rendered (i.e. if I use debug($event->errors());). But when I create the input fields, there are no error messages for the bad dates. They all have the submitted values in them, just no errors.
Tried using "date" instead of "dates" for the base of the field names (e.g. date.0.date instead of dates.0.date), to see if it was skipping the error check due to "dates" not being a column in the table, but this just caused a validation error at the point where I validate the rest of the data, so I didn't pursue that option any further.
I have some code elsewhere that sets the errors on associated entities which, if translated to this situation, would look like $event->dates[0]->errors($e->errors('date'));, but $event isn't associated with something called dates, so I don't see how to make this work here. I tried creating dates as an array in the $event entity, but assigning $event['dates'][$i] = $this->Events->newEntity(...); results in a PHP warning: "Indirect modification of overloaded element has no effect", and the entity is thrown away. Maybe manually create some ArrayContext object and assign the errors into that? Not immediately obvious to me how to do that.

Related

update a field-collection from a computed field

I'm able to retrieve the fields of the field collection attached to my content type, using codes like :
foreach ($entity->field_collection[LANGUAGE_NONE] as $line) {..}
or from an entity wrapper.
But I'm definitely unable to update the collection fields with values computed in the computed field, like I usually do with other CCK fields, like :
$entity->field_regular[LANGUAGE_NONE][0]['value'] = $value ;
then it is saved normally, as if I edited field_regular 'by hand'.
with collection this won't work (does nothing visible) :
$entity->field_collection[LANGUAGE_NONE][$key]['field_coll_field0'][LANGUAGE_NONE][0]['value'] = $value ;
// entity wrapper way
$coll = entity_load('field_collection_item', array($line['entity']->item_id));
$wcoll = entity_metadata_wrapper('field_collection_item', $coll[$key);
$wcoll->field_coll_field0->set($value) ;
any save() methods gives me a blank page (infinite cgi loop) :
entity_save('field_collection_item',$coll);
wcoll->save();
what should I know to programmatically save collection fields ?
thanks, Jerome
Please check Entity metadata wrappers doc page with simple example how to update a field collection, for example:
<?php
// Populate the fields.
$ewrapper = entity_metadata_wrapper('node', $node);
$ewrapper->field_lead_contact_name->set($contact_name);
$ewrapper->field_lead_contact_phone->set($contact_phone);
$ewrapper->field_lead_contact_email->set($contact_email);
// Create the collection entity and set it's "host".
$collection = entity_create('field_collection_item', array('field_name' => 'field_facilities_requested'));
$collection->setHostEntity('node', $node);
// Now define the collection parameters.
$cwrapper = entity_metadata_wrapper('field_collection_item', $collection);
$cwrapper->field_facility->set(intval($offset));
$cwrapper->save();
// Save.
$ewrapper->save();
?>
So probably you need to do as below:
try {
// Entity wrapper way.
$coll = entity_load('field_collection_item', array($line['entity']->item_id));
$wcoll = entity_metadata_wrapper('field_collection_item', $coll);
$wcoll->field_coll_field0 = $value;
$wcoll->save();
} catch (Exception $e) {
drupal_set_message(t('Error message: #error.',
array('#error' => $e->getMessage())), 'error');
watchdog_exception('MYMODULE', $e);
}
Adding try/catch should prevent you from having a blank page. However if you've still an issue, check watchdog logs or any errors in PHP log file.

Fetching values of a particular field from an array to a variable in cakephp

In cakephp, I executed this query to find the id field value of the corresponding username
$count = $this->User->find('all',array('conditions'=>array('User.username' => $n)))
How I will get take the value of a field from this array result to a variable?
I am new to cakephp and any help will be very useful.
Well, after calling find, you will notice that $count will be populated if something was brought from the database. I would change something, though, I would use "first" instead of "all" because you are finding only one record.
You can either use this
//in your controller
$count = $this->User->find('first',
array('conditions'=>array('User.username' => $n)));
//and set the variable to be used in the view in this way
$this->set('yourId', $count['User']['id']);
Then in your view
echo $yourId;
Or, you can also do this
$yourId = $this->User->field('id', array('User.username' => $n));
$this->set(compact('yourId'));
and then in your view
echo $yourId

Settings page and multi edit rows

I would like to make a configuration page like this for my CMS
http://img4.hostingpics.net/pics/72087272ok.png
I want this page (admin/settings/index) gets me the various settings (ID 1 to 21) came to my table and in case of change, I can, in this form, do an update
After 3 days of not especially fruitful research I found something. I put in my SettingsController:
admin_index public function () {
if (!empty($this->data)) {
$this->Setting->saveAll($this->request->data['Setting']);
} else {
$this->request->data['Setting'] = Set::combine($this->Setting->find('all'),
'{n}. Setting.id', '{n}. Setting');
}
}
and my view:
<?php
echo $this->Form->create('Setting', array ('class' => 'form-horizontal'));
foreach($this->request->data['Setting'] as $key => $value) {
echo $this->Form->input('Setting.' . $key . '.pair');
echo $this->Form->input('Setting.' . $key . '.id');
}
echo $this->Form->end(array('class' => 'btn btn-primary', 'label' => 'Save',
'value' => 'Update!', 'div' => array ('class' => 'form-actions')));
?>
he is recovering well all my information, I can even update. The trouble is that I do not really know how I can do to get the same result as my first screenshot. He puts everything in the textarea while in some cases I want the checkbox and / or drop-down.
please help me or explain to me how to make a page that retrieves configuration information from my table and allows me to edit without use an address like admin/settings/edit/ID
My table settings is something like this here
http://img4.hostingpics.net/pics/724403settings.png
If you are asking about how to format the output to produce a "settings" page, you will need to do it manually or add some kind of metadata to your table schema, such as "field_type".
Now Cake will automatically generate a checkbox for a boolean or tiny int, for example, but as you have varchars, ints, texts and so on, this won't work for you (pair is presumably varchar or text to allow for the different possible values.
You cannot really automagically generate the outputs you want without telling Cake.
One option would be to add a field_type to the table, so:
key value field
site_name My Site input
site_online 1 checkbox
meta_desc [some text] textarea
and something like
foreach($settings as $setting) {
echo $this->Form->{$setting['field']}($setting['key'],
array('value' => $setting['value']));
}
might do what you are after.
Or if($key=='site_name') // output a textbox
but this is not exactly ideal.

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.

Need to make full names in cakePHP

If I have a person model with first_name and last_name, how do I create and display a full_name? I would like to display it at the top of my Edit and View views (i.e. "Edit Frank Luke") and other places. Simply dropping echoes to first_name and last_name isn't DRY.
I'm sorry if this is a very simple question, but nothing has yet worked.
Thank you,
Frank Luke
Edit for clarity: Okay, I have a function on the person model.
function full_name() {
return $this->Person->first_name . ' ' . $this->Person->last_name;
}
In the view, I call
echo $person['Person']['full_name']
This gives me a notice that I have an undefined index. What is the proper way to call the function from the view? Do I have to do it in the controller or elsewhere?
If what you are wanting is just to display a full name, and never need to do any database actions (comparisons, lookups), I think you should just concatenate your fields in the view.
This would be more aligned with the MVC design pattern. In your example you just want to view information in your database in a different way.
Since the action of concatenating is simple you probably don't save much code by placing it in a separate function. I think its easiest to do just in the view file.
If you want to do more fancy things ( ie Change the caps, return a link to the user ) I would recommend creating an element which you call with the Users data.
The arrays set by the save() method only return fields in the datbase, they do not call model functions. To properly use the function above (located in your model), you will need to add the following:
to the controller, in the $action method:
$this->set( 'fullname', $this->Person->full_name();
// must have $this-Person->id set, or redefine the method to include $person_id
in the view,
echo $fullname;
Basically, you need to use the controller to gather the data from the model, and assign it to the controller. It's the same process as you have before, where you assign the returned data from the find() call to the variable in the view, except youre getting the data from a different source.
There are multiple ways of doing this. One way is to use the afterFind-function in a model-class.
See: http://book.cakephp.org/view/681/afterFind.
BUT, this function does not handle nested data very well, instead, it doesn't handles it al all!
Therefore I use the afterfind-function in the app_model that walks through the resultset
function afterFind($results, $primary=false){
$name = isset($this->alias) ? $this->alias : $this->name;
// see if the model wants to attach attributes
if (method_exists($this, '_attachAttributes')){
// check if array is not multidimensional by checking the key 'id'
if (isset($results['id'])) {
$results = $this->_attachAttributes($results);
} else {
// we want each row to have an array of required attributes
for ($i = 0; $i < sizeof($results); $i++) {
// check if this is a model, or if it is an array of models
if (isset($results[$i][$name]) ){
// this is the model we want, see if it's a single or array
if (isset($results[$i][$name][0]) ){
// run on every model
for ($j = 0; $j < sizeof($results[$i][$name]); $j++) {
$results[$i][$name][$j] = $this->_attachAttributes($results[$i][$name][$j]);
}
} else {
$results[$i][$name] = $this->_attachAttributes($results[$i][$name]);
}
} else {
if (isset($results[$i]['id'])) {
$results[$i] = $this->_attachAttributes($results[$i]);
}
}
}
}
}
return $results;
}
And then I add a _attachAttributes-function in the model-class, for e.g. in your Person.php
function _attachAttributes($data) {
if (isset($data['first_name']) && isset($data['last_name'])) {
$data['full_name'] = sprintf("%s %s %s", $data['first_name'], $data['last_name']);
}
return $data;
}
This method can handle nested modelData, for e.g. Person hasMany Posts then this method can also attachAttributes inside the Post-model.
This method also keeps in mind that the linked models with other names than the className are fixed, because of the use of the alias and not only the name (which is the className).
You must use afterFind callback for it.
You would probably need to take the two fields that are returned from your database and concatenate them into one string variable that can then be displayed.
http://old.nabble.com/Problems-with-CONCAT-function-td22640199.html
http://teknoid.wordpress.com/2008/09/29/dealing-with-calculated-fields-in-cakephps-find/
Read the first one to find out how to use the 'fields' key i.e. find( 'all', array( 'fields' => array( )) to pass a CONCAT to the CakePHP query builder.
The second link shows you how to merge the numeric indexes that get returned when you use custom fields back into the appropriate location in the returned results.
This should of course be placed in a model function and called from there.

Resources