3

I have a problem saving fields inside a field collection. Here is what I do: I have a node and one of its fields is field collection. One of the collected fields is an audio file and the other three are description, bitrate and duration. I.e. it looks like this:

Node

  • FieldCollection field
    • AudioFile collected field
    • Duration collected field
    • Bitrate collected field

I wanted my users to upload audio files on node editing and then programmatically fill bitrate and duration fields on presave using getId3 library. But it seems I can't just set bitrate and duration on presave and wait for node_save to save them to db. Here is a piece of code that successfully sets both bitrate and duration but doesn't save anything to db.

require_once('sites/all/libraries/getid3/getid3/getid3.php');
function mymodule_node_presave($node)
{
    $wrapper = entity_metadata_wrapper('node', $node);

    // Initialize getID3 engine
    $getID3 = new getID3;
    foreach($wrapper->field_song_with_params as $i => $value)
    {
        //getting file info
        $mp3FileInfo = $wrapper->field_song_with_params[$i]->field_mp3->value();
        $id3_fileinfo = $getID3->analyze(drupal_realpath($mp3FileInfo['uri']));

        //setting duration and bitrate
        $wrapper->field_song_with_params[$i]->field_mp3_duration->set($id3_fileinfo['playtime_string']);
        $wrapper->field_song_with_params[$i]->field_mp3_bitrate->set($id3_fileinfo['bitrate']);
    }unset($i);

    //$wrapper->save(); Calling this results in a fatal connectivity error
}

So what am I doing wrong and how to set it right?

UPDATE: After spending about 3 days on that I figured it out. I'll provide the code here for there may be another unlucky being dealing with updating Field Collection fields automatically, but there's apparently no 'graceful' way to deal with Field Collections (or at least I don't know of such). The code is lame, be warned. But it works.

1) First we add a handler that will be called on form submit. I need a handler only on a certain type of nodes called article_song that contain Field Collection fields.:

function mymodule_form_article_song_node_form_alter(&$form, &$form_state, $form_id)
{
    $form['#submit'][] = 'my_audio_file_properties_submit_handler';
}

2) Then there comes an abomination. It's heavily commented and I won't go into any details here. field_song_with_params is the name of the Field Collection field. field_mp3 is a collected field that holds uploaded audio file, field_mp3_duration and field_mp3_bitrate are collected fields containing duration and bitrate respectively.

function my_audio_file_properties_submit_handler(&$form, &$form_state)
{
    // Initialize getID3 engine
    $getID3 = new getID3;

    // here is how audio file field collection looks like on form
    $song_field_collection_on_form = $form['field_song_with_params'][LANGUAGE_NONE];

    //for every audio file and its params do the following:
    foreach($song_field_collection_on_form as $i => $song_collected_fields)
    {
        //need to check that $i is a real index of a FieldCollection entity.
        //Something belonging to $song_field_collection_on_form is not necessarily a FieldCollection
        //there are other properties there like #theme and so on.
        if(is_array($song_collected_fields) &&
            array_key_exists('#entity_type', $song_collected_fields) &&
            $song_collected_fields['#entity_type'] == 'field_collection_item')
        {
            //Getting file id value
            $file_id = $song_collected_fields['field_mp3'][LANGUAGE_NONE][0]['fid']['#value'];

            //if it is something meaningful (yeah it can be something crazy)
            if($file_id != 0)
            {
                //load file properties from db
                $file_info = file_load($file_id);
                $id3_fileinfo = $getID3->analyze(drupal_realpath($file_info->uri));

                // here is an FieldCollection entity at $form_state that
                // needs to be updated in order to tell drupal that value
                // should persist to db.
                // Normally people use something like form_set_value
                // http://api.drupal.org/api/drupal/includes!form.inc/function/form_set_value/7
                // but to work with it a value must be somewhere in a nested array
                // and in this case a value is in a FieldCollection Entity
                // that in its turn is in a nested array
                $song_field_entity_in_form_state = $form_state
                                                    ['values']
                                                    ['field_song_with_params']
                                                    [LANGUAGE_NONE]
                                                    [$i]
                                                    ['entity'];

                //setting duration
                $song_field_entity_in_form_state->field_mp3_duration[LANGUAGE_NONE][0]['value'] = $id3_fileinfo['playtime_string'];
                //setting bitrate
                $song_field_entity_in_form_state->field_mp3_bitrate[LANGUAGE_NONE][0]['value'] = $id3_fileinfo['bitrate'];
            }
        }
    }
}
6
  • Could you add the var_dump() or dpm() of $node that gets passed to this function? I just did a simple test with hook_node_presave and it does seem to save the modifications of $node from this hook. So you shouldn't need to call any saves from here.
    – Beebee
    Mar 29, 2012 at 11:07
  • @Baysaa , Here is a truncated var_dump() of the $node on hook_presave: object(stdClass)#80 (51) { ["vid"]=> string(1) "6" ["uid"]=> string(1) "1" ["title"]=> string(26) "Тэки ва икуман" <etc etc> ["field_song_with_params"]=> array(1) { ["und"]=> array(1) { [0]=> array(1) { ["value"]=> string(2) "14" } } } }
    – Ari Linn
    Mar 29, 2012 at 19:25
  • From your var_dump, it looks like field_song_with_params only has value key-value pair, so it doesn't have field_mp3, field_mp3_duration, field_mp3_bitrate subfields am I right? I assume Drupal won't let you just create these on the fly.
    – Beebee
    Mar 30, 2012 at 8:07
  • @Baysaa , I suspected this but it's kinda bad. Is there any way to intercept Drupal creation/saving of Field Collections on node_save() and edit it? Or is there anything like hook_node_postsave() that can be used to load the already saved Field Collection item and update its collected fields?
    – Ari Linn
    Mar 30, 2012 at 11:40
  • there's a module called Computed Field (drupal.org/project/computed_field) which you could have a look at. But basically, seems to me like you could just create the fields on the form (or include them) and just hide them during the node creation (use hook_form_alter and set #access of the fields you want to hide to FALSE). Then after the form gets submitted, you can set their values, which is basically what the above module does.
    – Beebee
    Mar 30, 2012 at 12:34

6 Answers 6

5

A good post giving examples on deleting/creating/modifying field collection items can be found here:

http://rajanmayekar.com/blog/programmatically-creating-deleting-modifying-field-collection-item-node

I actually had to implement this myself and I did it a bit different. This is how I programmatically add field collection items on node insert/update. There's no logic on how to update the items though, with this they are just created.

/**
 * Implements hook_entity_insert().
 *
 * We use this hook as it's called later than node_insert().
 */
function HOOK_entity_insert($node, $type) {
  module_field_collection_stuff($node, $type);
}

/**
 * Implements hook_entity_update().
 */
function HOOK_entity_update($node, $type) {
  module_field_collection_stuff($node, $type);
}

/**
 * Do your magic
 */
function module_field_collection_stuff($node, $type) {
  if ($type != 'node' || $node->type != 'my_content_type') {
    return;
  }

  // Create a dummy node so we can update the real node without
  // triggering all the node fields hooks.
  $update = new stdClass();
  $update->nid = $node->nid;
  $update->type = $node->type;
  $update->status = $node->status;
  $update->language = $node->language;

  $values['field_name'] = 'field_collection_field';
  $values['field_item_first'][LANGUAGE_NONE][0]['value'] = 'value';
  $values['field_item_second'][LANGUAGE_NONE][0]['value'] = 'second';

  // Create new field collection item.
  $field_collection_item = entity_create('field_collection_item', $values);

  // Attach it to the  node
  $field_collection_item->setHostEntity('node', $update, LANGUAGE_NONE, true);
  // Save field-collection item, without trigger the nodes own save.
  // This is because you might want to insert multiple field collection items.
  $field_collection_item->save(TRUE);

  // Update the fields.
  field_attach_presave('node', $update);
  field_attach_update('node', $update);
}
1
  • I really could kiss you for this! Fantastic, simple and efficient solution. You should write a blog post for this. Btw, that link no longer works (although it is still accessible on wayback machine).
    – Nadine
    Jul 22, 2014 at 11:42
0

I dont know if it can help you here I update my node (add anew field collection)

 $mynode = node_load(25);
            $field_collection_item = entity_create('field_collection_item', array('field_name' => 'field_joinedmembers'));
            $field_collection_item->setHostEntity('node', $mynode);
            $field_collection_item->field_members_name[LANGUAGE_NONE][]['uid'] = $user->uid;
            $field_collection_item->field_initiative[LANGUAGE_NONE][]['nid'] = arg(2);
            $field_collection_item->save();
1
  • It's a good solution but the initial post is about updating existing Field Collection items, not adding new ones.
    – Ari Linn
    Apr 8, 2012 at 13:59
0
  if (isset($profile->field_childrens_info[LANGUAGE_NONE][$i])) {
    // update
    $fc_item = reset(entity_load('field_collection_item', array($profile->field_childrens_info[LANGUAGE_NONE][$i]['value'])));
  }
1
  • You should really try to explain what you are doing with this snippet, so that the original asker knows what their problem is and why.
    – Chapabu
    Oct 20, 2012 at 16:31
0

The call to $wrapper->save() will work if you switch to user 1 first:

// switch to user 1 to update field_collection_item
global $user;
$original_user = $user;
$old_state = drupal_save_session(FALSE);
$user = user_load(1);

$wrapper->save();

// be sure to switch back to the original user
$user = $original_user;
drupal_save_session($old_state);

The user-switching code comes from a feature request on Views Bulk Operations, https://drupal.org/node/1334088#comment-5870512.

This indicates some kind of permissions problem with updating field collections, but I haven't been able to sort it out that way.

0

This is my contribution to answer the original question on how to update a field_collection before it is saved (and avoid using the proposed technique on form hooks).

I had a field collection attached to a node and I wanted to update one field of the collection. This can be achieved through a call to hook_field_collection_item_presave(FieldCollectionItemEntity $field_collection_item).

function mymodule_field_collection_item_presave(FieldCollectionItemEntity $field_collection_item) { $field_collection_item->field_addr[LANGUAGE_NONE]['0'] = "XXX"; }

0

you can use below code to save/update field collection details by item id

<?PHP 
global $user;
$field_collection_item = entity_load('field_collection_item', array($value));
$field_collection_item[$value]->field_members_name[LANGUAGE_NONE][]['uid'] = $user->uid;
$field_collection_item[$value]->field_initiative[LANGUAGE_NONE][]['nid'] = arg(2);
$field_collection_item[$value]->save();
?>

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.