Index: modules/field/field.install =================================================================== RCS file: /cvs/drupal/drupal/modules/field/field.install,v retrieving revision 1.4 diff -u -r1.4 field.install --- modules/field/field.install 10 Mar 2009 09:45:31 -0000 1.4 +++ modules/field/field.install 13 Apr 2009 21:42:05 -0000 @@ -58,6 +58,12 @@ 'not null' => TRUE, 'default' => 0, ), + 'translatable' => array( + 'type' => 'int', + 'size' => 'tiny', + 'not null' => TRUE, + 'default' => 0, + ), 'active' => array( 'type' => 'int', 'size' => 'tiny', Index: modules/field/field.module =================================================================== RCS file: /cvs/drupal/drupal/modules/field/field.module,v retrieving revision 1.7 diff -u -r1.7 field.module --- modules/field/field.module 26 Mar 2009 13:31:24 -0000 1.7 +++ modules/field/field.module 13 Apr 2009 21:42:06 -0000 @@ -57,6 +57,11 @@ /** * TODO */ +define('FIELD_LANGUAGE_NEUTRAL', '0'); + +/** + * TODO + */ define('FIELD_BEHAVIOR_NONE', 0x0001); /** * TODO Index: modules/field/field.attach.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/field/field.attach.inc,v retrieving revision 1.10 diff -u -r1.10 field.attach.inc --- modules/field/field.attach.inc 13 Apr 2009 05:18:17 -0000 1.10 +++ modules/field/field.attach.inc 13 Apr 2009 21:42:04 -0000 @@ -171,24 +171,39 @@ foreach ($instances as $instance) { $field_name = $instance['field_name']; $field = field_info_field($field_name); - $items = isset($object->$field_name) ? $object->$field_name : array(); - + + if (empty($object->$field_name)) { + // Initialize field translations according to the available languages. + foreach(_field_available_languages($field, $instance) as $language) { + $field_translations[$language] = array(); + } + } + else { + $field_translations = $object->$field_name; + } + $function = $default ? 'field_default_' . $op : $field['module'] . '_field_' . $op; if (drupal_function_exists($function)) { - $result = $function($obj_type, $object, $field, $instance, $items, $a, $b); - if (is_array($result)) { - $return = array_merge($return, $result); - } - else if (isset($result)) { - $return[] = $result; + // Iterate over all the field translations. + foreach ($field_translations as $language => $items) { + // TODO: Should we introduce a $language parameter? + $instance['language'] = $language; + $result = $function($obj_type, $object, $field, $instance, $items, $a, $b); + // Store the result into per-language separate fields. + if (is_array($result)) { + $return[$language] = isset($return[$language]) ? array_merge($return[$language], $result) : $result; + } + else if (isset($result)) { + $return[$language][] = $result; + } + // Put back the altered items in the object, if the field was present to + // begin with (avoid replacing missing field with empty array(), those are + // not semantically equivalent on update). + if (isset($object->{$field_name}[$language])) { + $object->{$field_name}[$language] = $items; + } } } - // Put back the altered items in the object, if the field was present to - // begin with (avoid replacing missing field with empty array(), those are - // not semantically equivalent on update). - if (isset($object->$field_name)) { - $object->$field_name = $items; - } } return $return; @@ -202,6 +217,33 @@ } /** + * TODO + */ +function _field_available_languages($field, $instance) { + static $field_languages; + + $field_name = $instance['field_name']; + if (!isset($field_languages[$field_name])) { + if ($field['translatable']) { + $available_languages = array_keys(language_list()); + $languages = isset($instance['languages']) ? $instance['languages'] : $available_languages; + // TODO: Do we need this hook invocation? + foreach (module_implements('field_languages') as $module) { + $function = $module . '_field_languages'; + $function($field, $instance, &$languages); + } + // Accept only available languages. + $field_languages[$field_name] = array_intersect($available_languages, $languages); + } + else { + $field_languages[$field_name] = array(FIELD_LANGUAGE_NEUTRAL); + } + } + + return $field_languages[$field_name]; +} + +/** * @} End of "defgroup field_attach" * * The rest of the functions in this file are not in a group, but @@ -226,8 +268,24 @@ */ function _field_attach_form($obj_type, $object, &$form, $form_state) { // TODO : something's not right here : do we alter the form or return a value ? - $form += (array) _field_invoke_default('form', $obj_type, $object, $form, $form_state); + $result = _field_invoke_default('form', $obj_type, $object, $form, $form_state); + // TODO: is this the right way? + $languages = language_list(); + $form['field_translations'] = array( + '#type' => 'fieldset', + '#title' => t('Field translations'), + '#tree' => TRUE + ); + foreach ($result as $language => $field_form) { + $form['field_translations'][$language] = array( + '#type' => 'fieldset', + '#title' => empty($language) ? t('Language neutral') : $languages[$language]->native, + '#tree' => TRUE, + ); + $form['field_translations'][$language] += (array) $field_form; + } + // Let other modules make changes to the form. foreach (module_implements('field_attach_form') as $module) { $function = $module . '_field_attach_form'; @@ -296,7 +354,7 @@ // Invoke the storage engine's hook_field_storage_load(): the field storage // engine loads the rest. $additions = module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'field_storage_load', $obj_type, $queried_objects, $age, $skip_fields); - + // First, merge the additions from the storage engine. foreach ($additions as $id => $obj_additions) { foreach ($obj_additions as $key => $value) { @@ -310,7 +368,7 @@ $additions[$id][$key] = $value; } } - + // TODO D7 : to be consistent we might want to make hook_field_load() accept // multiple objects too. Which forbids going through _field_invoke(), but // requires manually iterating the instances instead. @@ -636,7 +694,6 @@ } return $output; - } /** Index: modules/field/field.default.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/field/field.default.inc,v retrieving revision 1.5 diff -u -r1.5 field.default.inc --- modules/field/field.default.inc 26 Mar 2009 13:31:24 -0000 1.5 +++ modules/field/field.default.inc 13 Apr 2009 21:42:05 -0000 @@ -12,21 +12,22 @@ function field_default_extract_form_values($obj_type, $object, $field, $instance, &$items, $form, &$form_state) { $field_name = $field['field_name']; - - if (isset($form_state['values'][$field_name])) { - $items = $form_state['values'][$field_name]; + $language = $instance['language']; + + if (isset($form_state['values']['field_translations'][$language][$field_name])) { + $items = $form_state['values']['field_translations'][$language][$field_name]; // Remove the 'value' of the 'add more' button. unset($items[$field_name . '_add_more']); // _field_invoke() does not add back items for fields not present in the // original $object, so we add them manually. - $object->{$field_name} = $items; + $object->{$field_name}[$language] = $items; } else { // The form did not include this field, for instance because of access // rules: make sure any existing value for the field stays unchanged. - unset($object->{$field_name}); + unset($object->{$field_name}[$language]); } } Index: modules/field/field.crud.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/field/field.crud.inc,v retrieving revision 1.9 diff -u -r1.9 field.crud.inc --- modules/field/field.crud.inc 30 Mar 2009 05:24:38 -0000 1.9 +++ modules/field/field.crud.inc 13 Apr 2009 21:42:05 -0000 @@ -46,6 +46,8 @@ * - cardinality (integer) * The number of values the field can hold. Legal values are any * positive integer or FIELD_CARDINALITY_UNLIMITED. + * - translatable (integer) + * Whether the field is translatable * - locked (integer) * TODO: undefined. * - module (string, read-only) @@ -204,6 +206,7 @@ $field += array( 'cardinality' => 1, + 'translatable' => 0, 'locked' => FALSE, 'settings' => array(), ); Index: modules/field/modules/field_sql_storage/field_sql_storage.module =================================================================== RCS file: /cvs/drupal/drupal/modules/field/modules/field_sql_storage/field_sql_storage.module,v retrieving revision 1.6 diff -u -r1.6 field_sql_storage.module --- modules/field/modules/field_sql_storage/field_sql_storage.module 30 Mar 2009 03:44:55 -0000 1.6 +++ modules/field/modules/field_sql_storage/field_sql_storage.module 13 Apr 2009 21:42:07 -0000 @@ -128,9 +128,17 @@ 'not null' => TRUE, 'description' => 'The sequence number for this data item, used for multi-value fields', ), + // @todo Consider an integer field for 'language'. + 'language' => array( + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'default' => '', + 'description' => 'The language for this data item.', + ), ), - 'primary key' => array('etid', 'entity_id', 'deleted', 'delta'), - // TODO : index on 'bundle' + 'primary key' => array('etid', 'entity_id', 'deleted', 'delta', 'language'), + // @todo Index on 'bundle'. ); // Add field columns. @@ -144,7 +152,7 @@ $revision = $current; $revision['description'] = 'Revision archive storage for field ' . $field['field_name']; $revision['revision_id']['description'] = 'The entity revision id this data is attached to'; - $revision['primary key'] = array('etid', 'revision_id', 'deleted', 'delta'); + $revision['primary key'] = array('etid', 'revision_id', 'deleted', 'delta', 'language'); return array( _field_sql_storage_tablename($field['field_name']) => $current, @@ -179,7 +187,7 @@ function field_sql_storage_field_storage_load($obj_type, $objects, $age, $skip_fields = array()) { $etid = _field_sql_storage_etid($obj_type); $load_current = $age == FIELD_LOAD_CURRENT; - + // Gather ids needed for each field. $field_ids = array(); $delta_count = array(); @@ -199,7 +207,7 @@ $field = field_info_field($field_name); $table = $load_current ? _field_sql_storage_tablename($field_name) : _field_sql_storage_revision_tablename($field_name); - + $results = db_select($table, 't') ->fields('t') ->condition('etid', $etid) @@ -209,16 +217,16 @@ ->execute(); foreach ($results as $row) { - if ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED || $delta_count[$row->entity_id][$field_name] < $field['cardinality']) { + if ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED || !empty($field['translatable']) || $delta_count[$row->entity_id][$field_name] < $field['cardinality']) { $item = array(); // For each column declared by the field, populate the item // from the prefixed database column. foreach ($field['columns'] as $column => $attributes) { $item[$column] = $row->{_field_sql_storage_columnname($field_name, $column)}; } - - // Add the item to the field values for the entity. - $additions[$row->entity_id][$field_name][] = $item; + + // Add the item to the field values for the entity according to its language. + $additions[$row->entity_id][$field_name][$row->language][$row->delta] = $item; $delta_count[$row->entity_id][$field_name]++; } } @@ -230,19 +238,21 @@ * Implementation of hook_field_storage_write(). */ function field_sql_storage_field_storage_write($obj_type, $object, $op, $skip_fields) { - list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $object); - $etid = _field_sql_storage_etid($obj_type); + $info = new stdClass(); + list($info->id, $info->vid, $info->bundle) = field_attach_extract_ids($obj_type, $object); + $info->etid = _field_sql_storage_etid($obj_type); + //$fieldable_info = field_info_fieldable_types($obj_type); - $instances = field_info_instances($bundle); + $instances = field_info_instances($info->bundle); foreach ($instances as $instance) { $field_name = $instance['field_name']; if (isset($skip_fields[$field_name])) { continue; } - - $table_name = _field_sql_storage_tablename($field_name); - $revision_name = _field_sql_storage_revision_tablename($field_name); - $field = field_read_field($field_name); + + $info->table_name = _field_sql_storage_tablename($field_name); + $info->revision_name = _field_sql_storage_revision_tablename($field_name); + $info->field = field_read_field($field_name); // Leave the field untouched if $object comes with no $field_name property. // Empty the field if $object->$field_name is NULL or an empty array. @@ -252,52 +262,62 @@ if (isset($object->$field_name) || property_exists($object, $field_name)) { // Delete and insert, rather than update, in case a value was added. if ($op == FIELD_STORAGE_UPDATE) { - db_delete($table_name)->condition('etid', $etid)->condition('entity_id', $id)->execute(); - if (isset($vid)) { - db_delete($revision_name)->condition('etid', $etid)->condition('entity_id', $id)->condition('revision_id', $vid)->execute(); + db_delete($info->table_name)->condition('etid', $info->etid)->condition('entity_id', $info->id)->execute(); + if (isset($info->vid)) { + db_delete($info->revision_name)->condition('etid', $info->etid)->condition('entity_id', $info->id)->condition('revision_id', $info->vid)->execute(); } } + // Store the items. + foreach ($object->$field_name as $language => $items) { + _field_sql_storage_field_storage_write_items($info, $items, $language); + } + } + } +} - if ($object->$field_name) { - // Prepare the multi-insert query. - $columns = array('etid', 'entity_id', 'revision_id', 'bundle', 'delta'); - foreach ($field['columns'] as $column => $attributes) { - $columns[] = _field_sql_storage_columnname($field_name, $column); - } - $query = db_insert($table_name)->fields($columns); - if (isset($vid)) { - $revision_query = db_insert($revision_name)->fields($columns); - } +/** + * Actually store the given field values with respect to the given language. + */ +function _field_sql_storage_field_storage_write_items($info, $items, $language = FIELD_LANGUAGE_NEUTRAL) { + if ($items) { + // Prepare the multi-insert query. + $columns = array('etid', 'entity_id', 'revision_id', 'bundle', 'delta', 'language'); + foreach ($info->field['columns'] as $column => $attributes) { + $columns[] = _field_sql_storage_columnname($info->field['field_name'], $column); + } + $query = db_insert($info->table_name)->fields($columns); + if (isset($info->vid)) { + $revision_query = db_insert($info->revision_name)->fields($columns); + } - $delta_count = 0; - foreach ($object->$field_name as $delta => $item) { - $record = array( - 'etid' => $etid, - 'entity_id' => $id, - 'revision_id' => $vid, - 'bundle' => $bundle, - 'delta' => $delta, - ); - foreach ($field['columns'] as $column => $attributes) { - $record[_field_sql_storage_columnname($field_name, $column)] = isset($item[$column]) ? $item[$column] : NULL; - } - $query->values($record); - if (isset($vid)) { - $revision_query->values($record); - } - - if ($field['cardinality'] != FIELD_CARDINALITY_UNLIMITED && ++$delta_count == $field['cardinality']) { - break; - } - } + $delta_count = 0; + foreach ($items as $delta => $item) { + $record = array( + 'etid' => $info->etid, + 'entity_id' => $info->id, + 'revision_id' => $info->vid, + 'bundle' => $info->bundle, + 'delta' => $delta, + 'language' => !empty($language) ? $language : FIELD_LANGUAGE_NEUTRAL, + ); + foreach ($info->field['columns'] as $column => $attributes) { + $record[_field_sql_storage_columnname($info->field['field_name'], $column)] = isset($item[$column]) ? $item[$column] : NULL; + } + $query->values($record); + if (isset($info->vid)) { + $revision_query->values($record); + } - // Execute the insert. - $query->execute(); - if (isset($vid)) { - $revision_query->execute(); - } + if ($info->field['cardinality'] != FIELD_CARDINALITY_UNLIMITED && empty($info->field['translatable']) && ++$delta_count == $info->field['cardinality']) { + break; } } + + // Execute the insert. + $query->execute(); + if (isset($info->vid)) { + $revision_query->execute(); + } } } @@ -384,4 +404,4 @@ ->condition('bundle', $bundle_old) ->execute(); } -} \ No newline at end of file +}