| 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 |
| 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); |
| 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, |
| 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 |
* ); |
* ); |
| 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 |
} |
} |
| 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); |
| 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 |
} |
} |
| 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 |
} |
} |
| 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 |
/** |
/** |
| 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) { |
| 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) { |
| 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 |
| 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(); |