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'];
}
}
}
}
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" } } } }
field_song_with_params
only hasvalue
key-value pair, so it doesn't havefield_mp3
,field_mp3_duration
,field_mp3_bitrate
subfields am I right? I assume Drupal won't let you just create these on the fly.