/[drupal]/contributions/modules/pbs/pbs.fieldapi.inc
ViewVC logotype

Diff of /contributions/modules/pbs/pbs.fieldapi.inc

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph | View Patch Patch

revision 1.11, Mon Sep 7 18:24:51 2009 UTC revision 1.12, Tue Sep 8 17:46:19 2009 UTC
# Line 18  Line 18 
18   */   */
19    
20  /**  /**
21     * If FALSE, all data stored in the per-bundle tables will also be
22     * stored by the default field storage engine.  If TRUE, all
23     * non-shared fields will only be stored in per-bundle tables, and
24     * shared fields will be stored based on PBS_DUPLICATE_SHARED_FIELDS.
25     *
26     * NOTE: This module no longer supports TRUE for this setting due to
27     * (at least) lack of hook_field_attach_query() and bulk delete support.
28     */
29    define('PBS_PRIMARY_STORAGE', FALSE);
30    
31    /**
32   * If TRUE, shared field data will be stored both in the per-bundle   * If TRUE, shared field data will be stored both in the per-bundle
33   * table for all of the field's bundles plus the default field storage   * table for all of the field's bundles plus the default field storage
34   * engine.  If FALSE, shared fields are only stored in per-bundle   * engine.  If FALSE, shared fields are only stored in per-bundle
# Line 81  function pbs_bundle_schema($bundle) { Line 92  function pbs_bundle_schema($bundle) {
92          'not null' => FALSE,          'not null' => FALSE,
93          'description' => 'The entity revision id this data is attached to, or NULL if the entity type is not versioned',          'description' => 'The entity revision id this data is attached to, or NULL if the entity type is not versioned',
94        ),        ),
95          'language' => array(
96            'type' => 'varchar',
97            'length' => 32,
98            'not null' => TRUE,
99            'default' => '',
100            'description' => 'The language for this data',
101          ),
102      ),      ),
103      'primary key' => array('etid', 'entity_id'),      'primary key' => array('etid', 'entity_id', 'language'),
104    );    );
105    
106    $instances = field_info_instances($bundle);    $instances = field_info_instances($bundle);
# Line 105  function pbs_bundle_schema($bundle) { Line 123  function pbs_bundle_schema($bundle) {
123    $revision = $current;    $revision = $current;
124    $revision['description'] = 'Revision archive for bundle table for bundle '. $bundle;    $revision['description'] = 'Revision archive for bundle table for bundle '. $bundle;
125    $revision['revision_id']['description'] = 'The entity revision id this data is attached to';    $revision['revision_id']['description'] = 'The entity revision id this data is attached to';
126    $revision['primary key'] = array('etid', 'revision_id');    $revision['primary key'] = array('etid', 'revision_id', 'language');
127    
128    return array(    return array(
129      pbs_tablename($bundle) => $current,      pbs_tablename($bundle) => $current,
# Line 134  function pbs_create_bundle_tables($bundl Line 152  function pbs_create_bundle_tables($bundl
152   * and delta.  This must be called any time the set of field instances   * and delta.  This must be called any time the set of field instances
153   * changes in any way.   * changes in any way.
154   *   *
155   * Suppose that a field F with columns C1 and C2 and with cardinality   * Suppose that a field name F, id Fid, with columns C1 and C2 and
156   * 2 has instances for bundles B1 and B2.  For field F, the map will   * with cardinality 2 has instances for bundles B1 and B2.  For field
157   * contain:   * F, the map will contain:
158   *   *
159   * $map = array(   * $map = array(
160   *   'B1' => array(   *   'B1' => array(
161   *     'F_C1_1' => array('F', 'C1', 1),   *     'F_C1_1' => array('F', 'Fid', 'C1', 1),
162   *     'F_C1_2' => array('F', 'C1', 2),   *     'F_C1_2' => array('F', 'Fid', 'C1', 2),
163   *     'F_C2_1' => array('F', 'C2', 1),   *     'F_C2_1' => array('F', 'Fid', 'C2', 1),
164   *     'F_C2_2' => array('F', 'C2', 2),   *     'F_C2_2' => array('F', 'Fid', 'C2', 2),
165   *     // other entries for B1   *     // other entries for B1
166   *   ),   *   ),
167   *   'B2' => array(   *   'B2' => array(
168   *     'F_C1_1' => array('F', 'C1', 1),   *     'F_C1_1' => array('F', 'Fid', 'C1', 1),
169   *     'F_C1_2' => array('F', 'C1', 2),   *     'F_C1_2' => array('F', 'Fid', 'C1', 2),
170   *     'F_C2_1' => array('F', 'C2', 1),   *     'F_C2_1' => array('F', 'Fid', 'C2', 1),
171   *     'F_C2_2' => array('F', 'C2', 2),   *     'F_C2_2' => array('F', 'Fid', 'C2', 2),
172   *     // other entries for B2   *     // other entries for B2
173   *   ),   *   ),
174   * );   * );
# Line 168  function pbs_precompute_bundle_map() { Line 186  function pbs_precompute_bundle_map() {
186          for ($delta = 0; $delta < $field['cardinality']; ++$delta) {          for ($delta = 0; $delta < $field['cardinality']; ++$delta) {
187            foreach ($schema['columns'] as $name => $spec) {            foreach ($schema['columns'] as $name => $spec) {
188              $column_name = _field_sql_storage_columnname($field['field_name'], $name) .'_'. $delta;              $column_name = _field_sql_storage_columnname($field['field_name'], $name) .'_'. $delta;
189              $map[$bundle][$column_name] = array($field['field_name'], $name, $delta);              $map[$bundle][$column_name] = array($field['field_name'], $field['id'], $name, $delta);
190            }            }
191          }          }
192        }        }
# Line 222  function pbs_synchronize_bundle($bundle) Line 240  function pbs_synchronize_bundle($bundle)
240   *   *
241   * Load available fields from an object's bundle table, preventing   * Load available fields from an object's bundle table, preventing
242   * them from being loaded from a field table.   * them from being loaded from a field table.
  *  
  * @param $obj_type  
  *   The entity type of objects being loaded, such as 'node' or  
  *   'user'.  
  * @param $objects  
  *   The array of objects for which to load data.  
  * @param $age  
  *   FIELD_LOAD_CURRENT to load the most recent revision for all  
  *   fields, or FIELD_LOAD_REVISION to load the version indicated by  
  *   each object.  
  * @param &$skip_fields  
  *   An array of whose keys are field names we should skip because  
  *   their data has already been loaded by another module.  
  * @return  
  *   Loaded field values are added to $additions and loaded field  
  *   names are set set as keys in $skip_fields.  
243   */   */
244  function pbs_field_attach_pre_load($obj_type, $objects, $age, &$skip_fields) {  function pbs_field_attach_pre_load($obj_type, $objects, $age, &$skip_fields) {
245    $etid = _field_sql_storage_etid($obj_type);    $etid = _field_sql_storage_etid($obj_type);
# Line 278  function pbs_field_attach_pre_load($obj_ Line 280  function pbs_field_attach_pre_load($obj_
280      // keyed by object id and field name.      // keyed by object id and field name.
281      foreach ($results as $row) {      foreach ($results as $row) {
282        foreach ($bundle_fields[$bundle] as $column_name => $tuple) {        foreach ($bundle_fields[$bundle] as $column_name => $tuple) {
283          list($field_name, $item_name, $delta) = $tuple;          list($field_name, $field_id, $item_name, $delta) = $tuple;
284          if (!isset($skip_fields[$field_name])) {          if (!isset($skip_fields[$field_id])) {
285            if (!is_null($row->{$column_name})) {            if (!is_null($row->{$column_name})) {
286              // We must explicitly include $delta in the assignment              // We must explicitly include $delta in the assignment
287              // below.  Consider a field 'foo' with two columns, e.g. value              // below.  Consider a field 'foo' with two columns, e.g. value
288              // and format. Without using $delta, the row containing              // and format. Without using $delta, the row containing
289              // foo_value_0 and foo_format_0 would end up stored as              // foo_value_0 and foo_format_0 would end up stored as
290              // $obj->foo[0]['value'] and $obj->foo[1/*BZZZ!*/]['format'].              // $obj->foo[0]['value'] and $obj->foo[1/*BZZZ!*/]['format'].
291              $objects[$row->entity_id]->{$field_name}[$delta][$item_name] = $row->{$column_name};              $objects[$row->entity_id]->{$field_name}[$row->language][$delta][$item_name] = $row->{$column_name};
292            }            }
293            else if (!isset($objects[$row->entity_id]->{$field_name})) {            else if (!isset($objects[$row->entity_id]->{$field_name})) {
294                // Even if we have no data for a field, $obj->field should
295                // always at least be an empty array.  Do NOT put a
296                // language key in; if we have no data for a field, it is wrong to
297                // create an empty language key for it.
298              $objects[$row->entity_id]->{$field_name} = array();              $objects[$row->entity_id]->{$field_name} = array();
299            }            }
300            $new_skips[$field_name] = 1;            $new_skips[$field_id] = 1;
301          }          }
302        }        }
303    
304        // field_attach_load() is specified to return data items indexed        if (!empty($objects[$row->entity_id]->{$field_name})) {
305        // from delta 0, regardless of how they were provided on save.          // field_attach_load() is specified to return data items indexed
306        // TODO: Wouldn't it be more efficient to do this on save          // from delta 0, regardless of how they were provided on save.
307        // instead of load?          // TODO: Wouldn't it be more efficient to do this on save
308        foreach ($bundle_fields[$bundle] as $column_name => $tuple) {          // instead of load?
309          list($field_name, $item_name, $delta) = $tuple;          // TODO: Why do this array_merge on field items once for every
310          if (!isset($skip_fields[$field_name])) {          // column in the field?  It seems like we only need to do it
311            $objects[$row->entity_id]->{$field_name} = array_merge($objects[$row->entity_id]->{$field_name});          // once per field.
312            foreach ($bundle_fields[$bundle] as $column_name => $tuple) {
313              list($field_name, $field_id, $item_name, $delta) = $tuple;
314              if (!isset($skip_fields[$field_id])) {
315                $objects[$row->entity_id]->{$field_name}[$row->language] = array_merge($objects[$row->entity_id]->{$field_name}[$row->language]);
316              }
317          }          }
318        }        }
319      }      }
# Line 335  function pbs_field_attach_pre_update($ob Line 346  function pbs_field_attach_pre_update($ob
346    $key = array('etid' => $etid, 'entity_id' => $id, 'revision_id' => $vid);    $key = array('etid' => $etid, 'entity_id' => $id, 'revision_id' => $vid);
347    
348    $instances = field_info_instances($bundle);    $instances = field_info_instances($bundle);
349    $row = array();    $rows = array();
350    foreach ($instances as $instance) {    foreach ($instances as $instance) {
351      // Leave the field untouched if $object comes with no $field_name property.      // The Rules:
352      // Empty the field if $object->$field_name is NULL or an empty array.      // * $object->field_name unset: leave data untouched
353        // * $object->field_name = array()/NULL: empty the field for all
354        //   (enabled) languages
355        // * $object->field_name[$lang] unset: leave data untouched for $lang
356        // * $object->field_name[$lang] = array()/NULL: empty the field for $lang
357        //
358        // Enabled/available languages for a single field are returned by
359        // field_multilingual_available_languages.
360    
361      // Function property_exists() is slower, so we catch the more frequent cases      // Function property_exists() is slower, so we catch the more frequent cases
362      // where it's an empty array with the faster isset().      // where it's an empty array with the faster isset().
363      $field_name = $instance['field_name'];      $field_name = $instance['field_name'];
364      $field = field_info_field($instance['field_name']);      $field = field_info_field($instance['field_name']);
365      if (!isset($skip_fields[$field['field_name']]) && $field['cardinality'] != FIELD_CARDINALITY_UNLIMITED) {      $field_id = $field['id'];
366        if (!isset($skip_fields[$field['id']]) && $field['cardinality'] != FIELD_CARDINALITY_UNLIMITED) {
367        if (isset($object->$field_name) || property_exists($object, $field_name)) {        if (isset($object->$field_name) || property_exists($object, $field_name)) {
368          for ($delta = 0; $delta < $field['cardinality']; ++$delta) {          // For each field, we are given data for one or more language
369            foreach ($field['columns'] as $name => $spec) {          // keys. The rule is to update data for the languages we're
370              $row[_field_sql_storage_columnname($field['field_name'], $name) .'_'. $delta] = isset($object->{$field['field_name']}[$delta][$name]) ? $object->{$field['field_name']}[$delta][$name] : NULL;          // given, and leave alone data for languages we're not
371            // given. We write all the data for each language on a single
372            // row, but we do not want to assume we have the same language
373            // keys for every field and cannot assume that we have all the
374            // fields for any language for which we have one field.
375            //
376            // So, we build up an array $rows keyed by language. Each
377            // $rows[$language] contains all the columns for all the
378            // fields which have data for that language, and no entries
379            // for the other columns.
380    
381            // TODO: The field might be NULL or array().  I need to figure out the
382            // correct behavior in that case; I think it will be UPDATEing
383            // all language rows for the object to have NULL for that
384            // field.  For now, just avoid an exception.
385            if (empty($object->{$field_name})) {
386              // if $object->$field_name is null/empty I think all you
387              // need to do is set NULL the field values for every row
388              // whose language is in the set returned by
389              // field_multilingual_available_languages($obj_type, $field)
390              $langs_items = array_fill_keys(field_multilingual_available_languages($obj_type, $field), array());
391            }
392            else {
393              $langs_items = $object->{$field_name};
394            }
395    
396            foreach ($langs_items as $language => &$items) {
397              for ($delta = 0; $delta < $field['cardinality']; ++$delta) {
398                foreach ($field['columns'] as $name => $spec) {
399                  $rows[$language][_field_sql_storage_columnname($field['field_name'], $name) .'_'. $delta] = isset($items[$delta][$name]) ? $items[$delta][$name] : NULL;
400                }
401            }            }
402          }          }
403        }        }
# Line 357  function pbs_field_attach_pre_update($ob Line 406  function pbs_field_attach_pre_update($ob
406        // still handled it.  Tell the field storage module to skip it,        // still handled it.  Tell the field storage module to skip it,
407        // but only if it is a non-shared field.        // but only if it is a non-shared field.
408        if (!PBS_DUPLICATE_SHARED_FIELDS || count($field['bundles']) == 1) {        if (!PBS_DUPLICATE_SHARED_FIELDS || count($field['bundles']) == 1) {
409          $all_fields[$field_name] = 1;          $all_fields[$field_id] = 1;
410        }        }
411      }      }
412    }    }
413    
414    if (count($row)) {    foreach ($rows as $language => $row) {
415      db_merge($bundle_table)->fields($row)->key($key)->execute();      if (count($row)) {
416      if (isset($vid)) {        $row['language'] = $key['language'] = $language;
417        db_merge($revision_table)->fields($row)->key($key)->execute();        db_merge($bundle_table)->fields($row)->key($key)->execute();
418          if (isset($vid)) {
419            db_merge($revision_table)->fields($row)->key($key)->execute();
420          }
421      }      }
422    }    }
423    
424    $skip_fields += $all_fields;    if (PBS_PRIMARY_STORAGE) {
425        $skip_fields += $all_fields;
426      }
427  }  }
428    
429  /**  /**
# Line 389  function pbs_field_attach_pre_insert($ob Line 443  function pbs_field_attach_pre_insert($ob
443   * that field appears.  Also, if the field's cardinality is greater   * that field appears.  Also, if the field's cardinality is greater
444   * than 1, query all the columnname_N columns for each condition.   * than 1, query all the columnname_N columns for each condition.
445   */   */
446  function pbs_field_attach_pre_query($field_name, $conditions, $result_format, $age, &$skip_field) {  function x_pbs_field_attach_pre_query($field_name, $conditions, $result_format, $age, &$skip_field) {
447    $field = field_info_field($field_name);    $field = field_info_field($field_name);
448    // We do not store unlimited cardinality fields.    // We do not store unlimited cardinality fields.
449    if ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED) {    if ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED) {
# Line 419  function pbs_field_attach_pre_query($fie Line 473  function pbs_field_attach_pre_query($fie
473      // Build the query.      // Build the query.
474      $query = db_select($table, 't');      $query = db_select($table, 't');
475      $query->join('field_config_entity_type', 'e', 't.etid = e.etid');      $query->join('field_config_entity_type', 'e', 't.etid = e.etid');
476      $query      $query->fields('e', array('type'));
     ->fields('e', array('type'));  
477      // TODO ->condition('deleted', 0)      // TODO ->condition('deleted', 0)
478      // Add fields, depending on the return format.      // Add fields, depending on the return format.
479      if ($load_values) {      if ($load_values) {
# Line 489  function pbs_field_attach_pre_query($fie Line 542  function pbs_field_attach_pre_query($fie
542    
543          // TODO: refactor with load          // TODO: refactor with load
544          foreach ($bundle_fields[$bundle] as $column_name => $tuple) {          foreach ($bundle_fields[$bundle] as $column_name => $tuple) {
545            list($field_name, $item_name, $delta) = $tuple;            list($field_name, $field_id, $item_name, $delta) = $tuple;
546            if (!is_null($row->{$column_name})) {            if (!is_null($row->{$column_name})) {
547              // We must explicitly include $delta in the assignment              // We must explicitly include $delta in the assignment
548              // below.  Consider a field 'foo' with two columns, e.g. value              // below.  Consider a field 'foo' with two columns, e.g. value
# Line 578  function pbs_field_create_instance($inst Line 631  function pbs_field_create_instance($inst
631   * When a field is removed from a bundle, just update the bundle map.   * When a field is removed from a bundle, just update the bundle map.
632   * We can't delete the columns yet because we need the data for batch   * We can't delete the columns yet because we need the data for batch
633   * delete.  The instance is marked deleted so we will just ignore the   * delete.  The instance is marked deleted so we will just ignore the
634   * data.  We will remove the columns when the batch delete cleanup   * data.
635   * process runs (not yet implemented).   *
636     * TODO: We will remove the columns when the batch delete cleanup
637     * process runs.
638   */   */
639  function pbs_field_delete_instance($instance) {  function pbs_field_delete_instance($instance) {
640    pbs_precompute_bundle_map();    pbs_precompute_bundle_map();

Legend:
Removed from v.1.11  
changed lines
  Added in v.1.12

  ViewVC Help
Powered by ViewVC 1.1.2