### Eclipse Workspace Patch 1.0 #P drupal_test_7 Index: modules/field/field.test =================================================================== RCS file: /cvs/drupal/drupal/modules/field/field.test,v retrieving revision 1.10 diff -u -r1.10 field.test --- modules/field/field.test 13 Apr 2009 05:18:17 -0000 1.10 +++ modules/field/field.test 14 Apr 2009 19:09:51 -0000 @@ -40,568 +40,630 @@ field_create_instance($this->instance); } - function testFieldAttachLoad() { - $entity_type = 'test_entity'; - $eid = 0; - - $etid = _field_sql_storage_etid($entity_type); - $columns = array('etid', 'entity_id', 'revision_id', 'delta', $this->field_name . '_value'); - - // Insert data for four revisions to the field revisions table - $query = db_insert($this->revision_table)->fields($columns); - for ($evid = 0; $evid < 4; ++$evid) { - $values[$evid] = array(); - // Note: we insert one extra value ('<=' instead of '<'). - for ($delta = 0; $delta <= $this->field['cardinality']; $delta++) { - $value = mt_rand(1, 127); - $values[$evid][] = $value; - $query->values(array($etid, $eid, $evid, $delta, $value)); - } - } - $query->execute(); - - // Insert data for the "most current revision" into the field table - $query = db_insert($this->table)->fields($columns); - foreach ($values[0] as $delta => $value) { - $query->values(array($etid, $eid, 0, $delta, $value)); - } - $query->execute(); - - // Load the "most current revision" - $entity = field_test_create_stub_entity($eid, 0, $this->instance['bundle']); - field_attach_load($entity_type, array($eid => $entity)); - foreach ($values[0] as $delta => $value) { - if ($delta < $this->field['cardinality']) { - $this->assertEqual($entity->{$this->field_name}[$delta]['value'], $value, "Value $delta is loaded correctly for current revision"); - } - else { - $this->assertFalse(array_key_exists($delta, $entity->{$this->field_name}), "No extraneous value gets loaded for current revision."); - } - } - - // Load every revision - for ($evid = 0; $evid < 4; ++$evid) { - $entity = field_test_create_stub_entity($eid, $evid, $this->instance['bundle']); - field_attach_load_revision($entity_type, array($eid => $entity)); - foreach ($values[$evid] as $delta => $value) { - if ($delta < $this->field['cardinality']) { - $this->assertEqual($entity->{$this->field_name}[$delta]['value'], $value, "Value $delta for revision $evid is loaded correctly"); - } - else { - $this->assertFalse(array_key_exists($delta, $entity->{$this->field_name}), "No extraneous value gets loaded for revision $evid."); - } - } - } - } - -// function testFieldAttachLoadMultiple() { - // TODO : test the 'multiple' aspect of load: - // define 2 bundles, 3 fields - // bundle1 gets instances of field1, field2 - // bundle2 gets instances of field1, field3 - // load 2 entities (one for each bundle) in a single load - // check that everything gets loaded ok. +// function testFieldAttachLoad() { +// $entity_type = 'test_entity'; +// $eid = 0; +// +// $etid = _field_sql_storage_etid($entity_type); +// $columns = array('etid', 'entity_id', 'revision_id', 'delta', $this->field_name . '_value'); +// +// // Insert data for four revisions to the field revisions table +// $query = db_insert($this->revision_table)->fields($columns); +// for ($evid = 0; $evid < 4; ++$evid) { +// $values[$evid] = array(); +// // Note: we insert one extra value ('<=' instead of '<'). +// for ($delta = 0; $delta <= $this->field['cardinality']; $delta++) { +// $value = mt_rand(1, 127); +// $values[$evid][] = $value; +// $query->values(array($etid, $eid, $evid, $delta, $value)); +// } +// } +// $query->execute(); +// +// // Insert data for the "most current revision" into the field table +// $query = db_insert($this->table)->fields($columns); +// foreach ($values[0] as $delta => $value) { +// $query->values(array($etid, $eid, 0, $delta, $value)); +// } +// $query->execute(); +// +// // Load the "most current revision" +// $entity = field_test_create_stub_entity($eid, 0, $this->instance['bundle']); +// field_attach_load($entity_type, array($eid => $entity)); +// foreach ($values[0] as $delta => $value) { +// if ($delta < $this->field['cardinality']) { +// $this->assertEqual($entity->{$this->field_name}[$delta]['value'], $value, "Value $delta is loaded correctly for current revision"); +// } +// else { +// $this->assertFalse(array_key_exists($delta, $entity->{$this->field_name}), "No extraneous value gets loaded for current revision."); +// } +// } +// +// // Load every revision +// for ($evid = 0; $evid < 4; ++$evid) { +// $entity = field_test_create_stub_entity($eid, $evid, $this->instance['bundle']); +// field_attach_load_revision($entity_type, array($eid => $entity)); +// foreach ($values[$evid] as $delta => $value) { +// if ($delta < $this->field['cardinality']) { +// $this->assertEqual($entity->{$this->field_name}[$delta]['value'], $value, "Value $delta for revision $evid is loaded correctly"); +// } +// else { +// $this->assertFalse(array_key_exists($delta, $entity->{$this->field_name}), "No extraneous value gets loaded for revision $evid."); +// } +// } +// } +// } +// +//// function testFieldAttachLoadMultiple() { +// // TODO : test the 'multiple' aspect of load: +// // define 2 bundles, 3 fields +// // bundle1 gets instances of field1, field2 +// // bundle2 gets instances of field1, field3 +// // load 2 entities (one for each bundle) in a single load +// // check that everything gets loaded ok. +//// } +// +// function testFieldAttachInsertAndUpdate() { +// $entity_type = 'test_entity'; +// $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); +// +// // Test insert. +// $values = array(); +// // Note: we try to insert one extra value ('<=' instead of '<'). +// // TODO : test empty values filtering and "compression" (store consecutive deltas). +// for ($delta = 0; $delta <= $this->field['cardinality']; $delta++) { +// $values[$delta]['value'] = mt_rand(1, 127); +// } +// $entity->{$this->field_name} = $rev_values[0] = $values; +// field_attach_insert($entity_type, $entity); +// +// $rows = db_select($this->table, 't')->fields('t')->execute()->fetchAllAssoc('delta', PDO::FETCH_ASSOC); +// foreach ($values as $delta => $value) { +// if ($delta < $this->field['cardinality']) { +// $this->assertEqual($rows[$delta][$this->field_name . '_value'], $value['value'], t("Value $delta is inserted correctly")); +// } +// else { +// $this->assertFalse(array_key_exists($delta, $rows), "No extraneous value gets inserted."); +// } +// } +// +// // Test update. +// $entity = field_test_create_stub_entity(0, 1, $this->instance['bundle']); +// $values = array(); +// // Note: we try to update one extra value ('<=' instead of '<'). +// for ($delta = 0; $delta <= $this->field['cardinality']; $delta++) { +// $values[$delta]['value'] = mt_rand(1, 127); +// } +// $entity->{$this->field_name} = $rev_values[1] = $values; +// field_attach_update($entity_type, $entity); +// $rows = db_select($this->table, 't')->fields('t')->execute()->fetchAllAssoc('delta', PDO::FETCH_ASSOC); +// foreach ($values as $delta => $value) { +// if ($delta < $this->field['cardinality']) { +// $this->assertEqual($rows[$delta][$this->field_name . '_value'], $value['value'], t("Value $delta is updated correctly")); +// } +// else { +// $this->assertFalse(array_key_exists($delta, $rows), "No extraneous value gets updated."); +// } +// } +// +// // Check that data for both revisions are in the revision table. +// // We make sure each value is stored correctly, then unset it. +// // When an entire revision's values are unset (remembering that we +// // put one extra value in $values per revision), unset the entire +// // revision. Then, if $rev_values is empty at the end, all +// // revision data was found. +// $results = db_select($this->revision_table, 't')->fields('t')->execute(); +// foreach ($results as $row) { +// $this->assertEqual($row->{$this->field_name . '_value'}, $rev_values[$row->revision_id][$row->delta]['value'], "Value {$row->delta} for revision {$row->revision_id} stored correctly"); +// unset($rev_values[$row->revision_id][$row->delta]); +// if (count($rev_values[$row->revision_id]) == 1) { +// unset($rev_values[$row->revision_id]); +// } +// } +// $this->assertTrue(empty($rev_values), "All values for all revisions are stored in revision table {$this->revision_table}"); +// +// // Check that update leaves the field data untouched if $object has no +// // $field_name key. +// unset($entity->{$this->field_name}); +// field_attach_update($entity_type, $entity); +// $rows = db_select($this->table, 't')->fields('t')->execute()->fetchAllAssoc('delta', PDO::FETCH_ASSOC); +// foreach ($values as $delta => $value) { +// if ($delta < $this->field['cardinality']) { +// $this->assertEqual($rows[$delta][$this->field_name . '_value'], $value['value'], t("Update with no field_name entry leaves value $delta untouched")); +// } +// } +// +// // Check that update with an empty $object->$field_name empties the field. +// $entity->{$this->field_name} = NULL; +// field_attach_update($entity_type, $entity); +// $rows = db_select($this->table, 't')->fields('t')->execute()->fetchAllAssoc('delta', PDO::FETCH_ASSOC); +// $this->assertEqual(count($rows), 0, t("Update with an empty field_name entry empties the field.")); +// } +// +// // Test insert and update with missing or invalid fields. For the +// // most part, these tests pass by not crashing or causing exceptions. +// function testFieldAttachSaveMissingData() { +// $entity_type = 'test_entity'; +// $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); +// +// // Insert: Field is missing +// field_attach_insert($entity_type, $entity); +// $count = db_result(db_query("SELECT COUNT(*) FROM {{$this->table}}")); +// $this->assertEqual($count, 0, 'Missing field results in no inserts'); +// +// // Insert: Field is NULL +// $entity->{$this->field_name} = NULL; +// field_attach_insert($entity_type, $entity); +// $count = db_result(db_query("SELECT COUNT(*) FROM {{$this->table}}")); +// $this->assertEqual($count, 0, 'NULL field results in no inserts'); +// +// // Add some real data +// $entity->{$this->field_name} = array(0 => array('value' => 1)); +// field_attach_insert($entity_type, $entity); +// $count = db_result(db_query("SELECT COUNT(*) FROM {{$this->table}}")); +// $this->assertEqual($count, 1, 'Field data saved'); +// +// // Update: Field is missing. Data should survive. +// unset($entity->{$this->field_name}); +// field_attach_update($entity_type, $entity); +// $count = db_result(db_query("SELECT COUNT(*) FROM {{$this->table}}")); +// $this->assertEqual($count, 1, 'Missing field leaves data in table'); +// +// // Update: Field is NULL. Data should be wiped. +// $entity->{$this->field_name} = NULL; +// field_attach_update($entity_type, $entity); +// $count = db_result(db_query("SELECT COUNT(*) FROM {{$this->table}}")); +// $this->assertEqual($count, 0, 'NULL field leaves no data in table'); // } - function testFieldAttachInsertAndUpdate() { - $entity_type = 'test_entity'; - $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); - - // Test insert. - $values = array(); - // Note: we try to insert one extra value ('<=' instead of '<'). - // TODO : test empty values filtering and "compression" (store consecutive deltas). - for ($delta = 0; $delta <= $this->field['cardinality']; $delta++) { - $values[$delta]['value'] = mt_rand(1, 127); - } - $entity->{$this->field_name} = $rev_values[0] = $values; - field_attach_insert($entity_type, $entity); - - $rows = db_select($this->table, 't')->fields('t')->execute()->fetchAllAssoc('delta', PDO::FETCH_ASSOC); - foreach ($values as $delta => $value) { - if ($delta < $this->field['cardinality']) { - $this->assertEqual($rows[$delta][$this->field_name . '_value'], $value['value'], t("Value $delta is inserted correctly")); - } - else { - $this->assertFalse(array_key_exists($delta, $rows), "No extraneous value gets inserted."); - } - } - - // Test update. - $entity = field_test_create_stub_entity(0, 1, $this->instance['bundle']); - $values = array(); - // Note: we try to update one extra value ('<=' instead of '<'). - for ($delta = 0; $delta <= $this->field['cardinality']; $delta++) { - $values[$delta]['value'] = mt_rand(1, 127); - } - $entity->{$this->field_name} = $rev_values[1] = $values; - field_attach_update($entity_type, $entity); - $rows = db_select($this->table, 't')->fields('t')->execute()->fetchAllAssoc('delta', PDO::FETCH_ASSOC); - foreach ($values as $delta => $value) { - if ($delta < $this->field['cardinality']) { - $this->assertEqual($rows[$delta][$this->field_name . '_value'], $value['value'], t("Value $delta is updated correctly")); - } - else { - $this->assertFalse(array_key_exists($delta, $rows), "No extraneous value gets updated."); - } - } - - // Check that data for both revisions are in the revision table. - // We make sure each value is stored correctly, then unset it. - // When an entire revision's values are unset (remembering that we - // put one extra value in $values per revision), unset the entire - // revision. Then, if $rev_values is empty at the end, all - // revision data was found. - $results = db_select($this->revision_table, 't')->fields('t')->execute(); - foreach ($results as $row) { - $this->assertEqual($row->{$this->field_name . '_value'}, $rev_values[$row->revision_id][$row->delta]['value'], "Value {$row->delta} for revision {$row->revision_id} stored correctly"); - unset($rev_values[$row->revision_id][$row->delta]); - if (count($rev_values[$row->revision_id]) == 1) { - unset($rev_values[$row->revision_id]); - } - } - $this->assertTrue(empty($rev_values), "All values for all revisions are stored in revision table {$this->revision_table}"); - - // Check that update leaves the field data untouched if $object has no - // $field_name key. - unset($entity->{$this->field_name}); - field_attach_update($entity_type, $entity); - $rows = db_select($this->table, 't')->fields('t')->execute()->fetchAllAssoc('delta', PDO::FETCH_ASSOC); - foreach ($values as $delta => $value) { - if ($delta < $this->field['cardinality']) { - $this->assertEqual($rows[$delta][$this->field_name . '_value'], $value['value'], t("Update with no field_name entry leaves value $delta untouched")); - } - } - - // Check that update with an empty $object->$field_name empties the field. - $entity->{$this->field_name} = NULL; - field_attach_update($entity_type, $entity); - $rows = db_select($this->table, 't')->fields('t')->execute()->fetchAllAssoc('delta', PDO::FETCH_ASSOC); - $this->assertEqual(count($rows), 0, t("Update with an empty field_name entry empties the field.")); - } - - // Test insert and update with missing or invalid fields. For the - // most part, these tests pass by not crashing or causing exceptions. - function testFieldAttachSaveMissingData() { - $entity_type = 'test_entity'; - $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); - - // Insert: Field is missing - field_attach_insert($entity_type, $entity); - $count = db_result(db_query("SELECT COUNT(*) FROM {{$this->table}}")); - $this->assertEqual($count, 0, 'Missing field results in no inserts'); - - // Insert: Field is NULL - $entity->{$this->field_name} = NULL; - field_attach_insert($entity_type, $entity); - $count = db_result(db_query("SELECT COUNT(*) FROM {{$this->table}}")); - $this->assertEqual($count, 0, 'NULL field results in no inserts'); - - // Add some real data - $entity->{$this->field_name} = array(0 => array('value' => 1)); - field_attach_insert($entity_type, $entity); - $count = db_result(db_query("SELECT COUNT(*) FROM {{$this->table}}")); - $this->assertEqual($count, 1, 'Field data saved'); - - // Update: Field is missing. Data should survive. - unset($entity->{$this->field_name}); - field_attach_update($entity_type, $entity); - $count = db_result(db_query("SELECT COUNT(*) FROM {{$this->table}}")); - $this->assertEqual($count, 1, 'Missing field leaves data in table'); - - // Update: Field is NULL. Data should be wiped. - $entity->{$this->field_name} = NULL; - field_attach_update($entity_type, $entity); - $count = db_result(db_query("SELECT COUNT(*) FROM {{$this->table}}")); - $this->assertEqual($count, 0, 'NULL field leaves no data in table'); - } - - function testFieldAttachViewAndPreprocess() { - $entity_type = 'test_entity'; - $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); - - // Populate values to be displayed. - $values = array(); - for ($delta = 0; $delta < $this->field['cardinality']; $delta++) { - $values[$delta]['value'] = mt_rand(1, 127); - } - $entity->{$this->field_name} = $values; - - // Simple formatter, label displayed. - $formatter_setting = $this->randomName(); - $this->instance['display'] = array( - 'full' => array( - 'label' => 'above', - 'type' => 'field_test_default', - 'settings' => array( - 'test_formatter_setting' => $formatter_setting, - ) - ), - ); - field_update_instance($this->instance); - $entity->content = field_attach_view($entity_type, $entity); - $output = drupal_render($entity->content); - $variables = field_attach_preprocess($entity_type, $entity); - $variable = $this->instance['field_name'] . '_rendered'; - $this->assertTrue(isset($variables[$variable]), "Variable $variable is available in templates."); - $this->content = $output; - $this->assertRaw($this->instance['label'], "Label is displayed."); - $this->content = $variables[$variable]; - $this->assertRaw($this->instance['label'], "Label is displayed (template variable)."); - foreach ($values as $delta => $value) { - $this->content = $output; - $this->assertRaw("$formatter_setting|{$value['value']}", "Value $delta is displayed, formatter settings are applied."); - $this->content = $variables[$variable]; - $this->assertRaw("$formatter_setting|{$value['value']}", "Value $delta is displayed, formatter settings are applied (template variable)."); - } - - // Label hidden. - $this->instance['display']['full']['label'] = 'hidden'; - field_update_instance($this->instance); - $entity->content = field_attach_view($entity_type, $entity); - $output = drupal_render($entity->content); - $variables = field_attach_preprocess($entity_type, $entity); - $this->content = $output; - $this->assertNoRaw($this->instance['label'], "Hidden label: label is not displayed."); - $this->content = $variables[$variable]; - $this->assertNoRaw($this->instance['label'], "Hidden label: label is not displayed (template variable)."); - - // Field hidden. - $this->instance['display'] = array( - 'full' => array( - 'label' => 'above', - 'type' => 'hidden', - - ), - ); - field_update_instance($this->instance); - $entity->content = field_attach_view($entity_type, $entity); - $output = drupal_render($entity->content); - $variables = field_attach_preprocess($entity_type, $entity); - $this->assertTrue(isset($variables[$variable]), "Hidden field: variable $variable is available in templates."); - $this->content = $output; - $this->assertNoRaw($this->instance['label'], "Hidden field: label is not displayed."); - foreach ($values as $delta => $value) { - $this->assertNoRaw($value['value'], "Hidden field: value $delta is not displayed."); - } - - // Multiple formatter. - $formatter_setting = $this->randomName(); - $this->instance['display'] = array( - 'full' => array( - 'label' => 'above', - 'type' => 'field_test_multiple', - 'settings' => array( - 'test_formatter_setting_multiple' => $formatter_setting, - ) - ), - ); - field_update_instance($this->instance); - $entity->content = field_attach_view($entity_type, $entity); - $output = drupal_render($entity->content); - $variables = field_attach_preprocess($entity_type, $entity); - $display = $formatter_setting; - foreach ($values as $delta => $value) { - $display .= "|$delta:{$value['value']}"; - } - $this->content = $output; - $this->assertRaw($display, "Multiple formatter: all values are displayed, formatter settings are applied."); - $this->content = $variables[$variable]; - $this->assertRaw($display, "Multiple formatter: all values are displayed, formatter settings are applied (template variable)."); - - // TODO: - // - check that the 'exclude' option works (if we keep it in core) - // - check display order with several fields - } - - function testFieldAttachDelete() { - $entity_type = 'test_entity'; - $rev[0] = field_test_create_stub_entity(0, 0, $this->instance['bundle']); - - // Create revision 0 - $values = array(); - for ($delta = 0; $delta < $this->field['cardinality']; $delta++) { - $values[$delta]['value'] = mt_rand(1, 127); - } - $rev[0]->{$this->field_name} = $values; - field_attach_insert($entity_type, $rev[0]); - - // Create revision 1 - $rev[1] = field_test_create_stub_entity(0, 1, $this->instance['bundle']); - $rev[1]->{$this->field_name} = $values; - field_attach_update($entity_type, $rev[1]); - - // Create revision 2 - $rev[2] = field_test_create_stub_entity(0, 2, $this->instance['bundle']); - $rev[2]->{$this->field_name} = $values; - field_attach_update($entity_type, $rev[2]); - - // Confirm each revision loads - foreach (array_keys($rev) as $vid) { - $read = field_test_create_stub_entity(0, $vid, $this->instance['bundle']); - field_attach_load_revision($entity_type, array(0 => $read)); - $this->assertEqual(count($read->{$this->field_name}), $this->field['cardinality'], "The test object revision $vid has {$this->field['cardinality']} values."); - } - - // Delete revision 1, confirm the other two still load. - field_attach_delete_revision($entity_type, $rev[1]); - foreach (array(0, 2) as $vid) { - $read = field_test_create_stub_entity(0, $vid, $this->instance['bundle']); - field_attach_load_revision($entity_type, array(0 => $read)); - $this->assertEqual(count($read->{$this->field_name}), $this->field['cardinality'], "The test object revision $vid has {$this->field['cardinality']} values."); - } - - // Confirm the current revision still loads - $read = field_test_create_stub_entity(0, 2, $this->instance['bundle']); - field_attach_load($entity_type, array(0 => $read)); - $this->assertEqual(count($read->{$this->field_name}), $this->field['cardinality'], "The test object current revision has {$this->field['cardinality']} values."); - - // Delete all field data, confirm nothing loads - field_attach_delete($entity_type, $rev[2]); - foreach (array(0, 1, 2) as $vid) { - $read = field_test_create_stub_entity(0, $vid, $this->instance['bundle']); - field_attach_load_revision($entity_type, array(0 => $read)); - $this->assertIdentical($read->{$this->field_name}, array(), "The test object revision $vid is deleted."); - } - $read = field_test_create_stub_entity(0, 2, $this->instance['bundle']); - field_attach_load($entity_type, array(0 => $read)); - $this->assertIdentical($read->{$this->field_name}, array(), "The test object current revision is deleted."); - } - - function testFieldAttachCreateRenameBundle() { - // Create a new bundle. This has to be initiated by the module so that its - // hook_fieldable_info() is consistent. - $new_bundle = 'test_bundle_' . drupal_strtolower($this->randomName()); - field_test_create_bundle($new_bundle, $this->randomName()); - - // Add an instance to that bundle. - $this->instance['bundle'] = $new_bundle; - field_create_instance($this->instance); - - // Save an object with data in the field. - $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); - $values = array(); - for ($delta = 0; $delta < $this->field['cardinality']; $delta++) { - $values[$delta]['value'] = mt_rand(1, 127); - } - $entity->{$this->field_name} = $values; - $entity_type = 'test_entity'; - field_attach_insert($entity_type, $entity); - - // Verify the field data is present on load. - $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); - field_attach_load($entity_type, array(0 => $entity)); - $this->assertEqual(count($entity->{$this->field_name}), $this->field['cardinality'], "Data are retrieved for the new bundle"); - - // Rename the bundle. This has to be initiated by the module so that its - // hook_fieldable_info() is consistent. - $new_bundle = 'test_bundle_' . drupal_strtolower($this->randomName()); - field_test_rename_bundle($this->instance['bundle'], $new_bundle); - - // Check that the instance definition has been updated. - $this->instance = field_info_instance($this->field_name, $new_bundle); - $this->assertIdentical($this->instance['bundle'], $new_bundle, "Bundle name has been updated in the instance."); - - // Verify the field data is present on load. - $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); - field_attach_load($entity_type, array(0 => $entity)); - $this->assertEqual(count($entity->{$this->field_name}), $this->field['cardinality'], "Bundle name has been updated in the field storage"); - } - - function testFieldAttachDeleteBundle() { - // Create a new bundle. This has to be initiated by the module so that its - // hook_fieldable_info() is consistent. - $new_bundle = 'test_bundle_' . drupal_strtolower($this->randomName()); - field_test_create_bundle($new_bundle, $this->randomName()); - - // Add an instance to that bundle. - $this->instance['bundle'] = $new_bundle; - field_create_instance($this->instance); + function testFieldAttachQuery() { + $cardinality = $this->field['cardinality']; - // Create a second field for the test bundle - $field_name = drupal_strtolower($this->randomName(). '_field_name'); - $table = _field_sql_storage_tablename($field_name); - $revision_table = _field_sql_storage_revision_tablename($field_name); - $field = array('field_name' => $field_name, 'type' => 'test_field', 'cardinality' => 1); - field_create_field($field); - $instance = array( - 'field_name' => $field_name, - 'bundle' => $this->instance['bundle'], - 'label' => $this->randomName(). '_label', - 'description' => $this->randomName(). '_description', - 'weight' => mt_rand(0, 127), - // test_field has no instance settings - 'widget' => array( - 'type' => 'test_field_widget', - 'settings' => array( - 'size' => mt_rand(0, 255)))); + // Create an additional bundle with an instance of the field. + field_test_create_bundle('test_bundle_1', 'Test Bundle 1'); + $instance = $this->instance; + $instance['bundle'] = 'test_bundle_1'; field_create_instance($instance); - // Save an object with data for both fields - $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); - $values = array(); - for ($delta = 0; $delta < $this->field['cardinality']; $delta++) { - $values[$delta]['value'] = mt_rand(1, 127); - } - $entity->{$this->field_name} = $values; - $entity->{$field_name} = array(0 => array('value' => 99)); - $entity_type = 'test_entity'; - field_attach_insert($entity_type, $entity); - - // Verify the fields are present on load - $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); - field_attach_load($entity_type, array(0 => $entity)); - $this->assertEqual(count($entity->{$this->field_name}), 4, "First field got loaded"); - $this->assertEqual(count($entity->{$field_name}), 1, "Second field got loaded"); - - // Delete the bundle. This has to be initiated by the module so that its - // hook_fieldable_info() is consistent. - field_test_delete_bundle($this->instance['bundle']); - - // Verify no data gets loaded - $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); - field_attach_load($entity_type, array(0 => $entity)); - $this->assertFalse(isset($entity->{$this->field_name}), "No data for first field"); - $this->assertFalse(isset($entity->{$field_name}), "No data for second field"); - - // Verify that the instances are gone - $this->assertFalse(field_read_instance($this->field_name, $this->instance['bundle']), "First field is deleted"); - $this->assertFalse(field_read_instance($field_name, $instance['bundle']), "Second field is deleted"); - } - - function testFieldAttachCache() { - // Create a revision - $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); - $values = array(); - for ($delta = 0; $delta < $this->field['cardinality']; $delta++) { - $values[$delta]['value'] = mt_rand(1, 127); - } - $entity->{$this->field_name} = $values; - - $noncached_type = 'test_entity'; - $cached_type = 'test_cacheable_entity'; - - // Non-cached type: - $cid = "field:$noncached_type:0:0"; - - // Confirm no initial cache entry - $this->assertFalse(cache_get($cid, 'cache_field'), 'Non-cached: no initial cache entry'); - - // Save, and confirm no cache entry - field_attach_insert($noncached_type, $entity); - $this->assertFalse(cache_get($cid, 'cache_field'), 'Non-cached: no cache entry on save'); - - // Load, and confirm no cache entry - field_attach_load($noncached_type, array(0 => $entity)); - $this->assertFalse(cache_get($cid, 'cache_field'), 'Non-cached: no cache entry on load'); - - // Cached type: - $cid = "field:$cached_type:0:0"; - - // Confirm no initial cache entry - $this->assertFalse(cache_get($cid, 'cache_field'), 'Cached: no initial cache entry'); - - // Save, and confirm no cache entry - field_attach_insert($cached_type, $entity); - $this->assertFalse(cache_get($cid, 'cache_field'), 'Cached: no cache entry on save'); - - // Load, and confirm cache entry - field_attach_load($cached_type, array(0 => $entity)); - $cache = cache_get($cid, 'cache_field'); - $this->assertEqual($cache->data[$this->field_name], $values, 'Cached: correct cache entry on load'); - - // Delete, and confirm no cache entry - field_attach_delete($cached_type, $entity); - $this->assertFalse(cache_get($cid, 'cache_field'), 'Cached: no cache entry on save'); - } - - // Verify that field_attach_validate() invokes the correct - // hook_field_validate. - function testFieldAttachValidate() { - $entity_type = 'test_entity'; - $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); + // Create two test objects, using two different types and bundles. + $entity_types = array('test_entity', 'test_cacheable_entity'); + $entities = array(field_test_create_stub_entity(0, 0, 'test_bundle'), field_test_create_stub_entity(1, 1, 'test_bundle_1')); - // Set up values to generate errors + // Create first test object with random data. $values = array(); - for ($delta = 0; $delta < $this->field['cardinality']; $delta++) { - $values[$delta]['value'] = -1; - $values[$delta]['_error_element'] = 'field_error_' . $delta; + for ($delta = 0; $delta < $cardinality; $delta++) { + $values[$delta] = mt_rand(1, 127); + $entities[0]->{$this->field_name}[$delta] = array('value' => $values[$delta]); + } + field_attach_insert($entity_types[0], $entities[0]); + + // Create second test object, sharing a value with the first one. + $common_value = $values[$cardinality - 1]; + $entities[1]->{$this->field_name} = array(array('value' => $common_value)); + field_attach_insert($entity_types[1], $entities[1]); + + // Query on the object's values. + for ($delta = 0; $delta < $cardinality; $delta++) { + $conditions = array(array('value', $values[$delta])); + $result = field_attach_query($this->field_name, $conditions); + $this->assertTrue(isset($result[$entity_types[0]][0]), t('Query on value %delta returns the object', array('%delta' => $delta))); } - // Arrange for item 1 not to generate an error - $values[1]['value'] = 1; - $entity->{$this->field_name} = $values; - try { - field_attach_validate($entity_type, $entity); - } - catch (FieldValidationException $e) { - $errors = $e->errors; - } - - foreach ($values as $delta => $value) { - if ($value['value'] != 1) { - $this->assertIdentical($errors[$this->field_name][$delta][0]['error'], 'field_test_invalid', "Error set on value $delta"); - $this->assertEqual(count($errors[$this->field_name][$delta]), 1, "Only one error set on value $delta"); - unset($errors[$this->field_name][$delta]); - } - else { - $this->assertFalse(isset($errors[$this->field_name][$delta]), "No error set on value $delta"); - } - } - $this->assertEqual(count($errors[$this->field_name]), 0, 'No extraneous errors set'); - } - - // Validate that FAPI elements are generated. This could be much - // more thorough, but it does verify that the correct widgets show up. - function testFieldAttachForm() { - $entity_type = 'test_entity'; - $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); - - $form = $form_state = array(); - field_attach_form($entity_type, $entity, $form, $form_state); - - $this->assertEqual($form[$this->field_name]['#title'], $this->instance['label'], "Form title is {$this->instance['label']}"); - for ($delta = 0; $delta < $this->field['cardinality']; $delta++) { - // field_test_widget uses 'textfield' - $this->assertEqual($form[$this->field_name][$delta]['value']['#type'], 'textfield', "Form delta $delta widget is textfield"); - } - } - - function testFieldAttachSubmit() { - $entity_type = 'test_entity'; - $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); - - // Build the form. - $form = $form_state = array(); - field_attach_form($entity_type, $entity, $form, $form_state); - - // Simulate incoming values. - $values = array(); - $weights = array(); - for ($delta = 0; $delta < $this->field['cardinality']; $delta++) { - $values[$delta]['value'] = mt_rand(1, 127); - // Assign random weight. - do { - $weight = mt_rand(0, $this->field['cardinality']); - } while (in_array($weight, $weights)); - $weights[$delta] = $weight; - $values[$delta]['_weight'] = $weight; - } - // Leave an empty value. 'field_test' fields are empty if empty(). - $values[1]['value'] = 0; - - $form_state['values'] = array($this->field_name => $values); - field_attach_submit($entity_type, $entity, $form, $form_state); - - asort($weights); - $expected_values = array(); - foreach ($weights as $key => $value) { - if ($key != 1) { - $expected_values[] = array('value' => $values[$key]['value']); - } - } - $this->assertIdentical($entity->{$this->field_name}, $expected_values, 'Submit filters empty values'); - } + // Query on a value not in the object. + do { + $value = mt_rand(1, 127); + } while (in_array($value, $values)); + $conditions = array(array('value', $value)); + $result = field_attach_query($this->field_name, $conditions); + $this->assertFalse(isset($result[$entity_types[0]][0]), t("Query on a value that is not in the object doesn't return the object")); + + // Query on the value shared by both objects. + + $conditions = array(array('value', $common_value)); + $result = field_attach_query($this->field_name, $conditions); + $this->assertTrue(isset($result[$entity_types[0]][0]) && isset($result[$entity_types[1]][1]), t('Query on a value common to both objects returns both objects')); + + $conditions = array(array('type', $entity_types[0]), array('value', $common_value)); + $result = field_attach_query($this->field_name, $conditions); + $this->assertTrue(isset($result[$entity_types[0]][0]) && !isset($result[$entity_types[1]][1]), t("Query on a value common to both objects and a 'type' condition only returns the relevant object")); + + $conditions = array(array('bundle', $entities[0]->fttype), array('value', $common_value)); + $result = field_attach_query($this->field_name, $conditions); + $this->assertTrue(isset($result[$entity_types[0]][0]) && !isset($result[$entity_types[1]][1]), t("Query on a value common to both objects and a 'bundle' condition only returns the relevant object")); + + $conditions = array(array('entity_id', $entities[0]->ftid), array('value', $common_value)); + $result = field_attach_query($this->field_name, $conditions); + $this->assertTrue(isset($result[$entity_types[0]][0]) && !isset($result[$entity_types[1]][1]), t("Query on a value common to both objects and an 'entity_id' condition only returns the relevant object")); + + // TODO : test field_attach_query_revisions + } +// +// function testFieldAttachViewAndPreprocess() { +// $entity_type = 'test_entity'; +// $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); +// +// // Populate values to be displayed. +// $values = array(); +// for ($delta = 0; $delta < $this->field['cardinality']; $delta++) { +// $values[$delta]['value'] = mt_rand(1, 127); +// } +// $entity->{$this->field_name} = $values; +// +// // Simple formatter, label displayed. +// $formatter_setting = $this->randomName(); +// $this->instance['display'] = array( +// 'full' => array( +// 'label' => 'above', +// 'type' => 'field_test_default', +// 'settings' => array( +// 'test_formatter_setting' => $formatter_setting, +// ) +// ), +// ); +// field_update_instance($this->instance); +// $entity->content = field_attach_view($entity_type, $entity); +// $output = drupal_render($entity->content); +// $variables = field_attach_preprocess($entity_type, $entity); +// $variable = $this->instance['field_name'] . '_rendered'; +// $this->assertTrue(isset($variables[$variable]), "Variable $variable is available in templates."); +// $this->content = $output; +// $this->assertRaw($this->instance['label'], "Label is displayed."); +// $this->content = $variables[$variable]; +// $this->assertRaw($this->instance['label'], "Label is displayed (template variable)."); +// foreach ($values as $delta => $value) { +// $this->content = $output; +// $this->assertRaw("$formatter_setting|{$value['value']}", "Value $delta is displayed, formatter settings are applied."); +// $this->content = $variables[$variable]; +// $this->assertRaw("$formatter_setting|{$value['value']}", "Value $delta is displayed, formatter settings are applied (template variable)."); +// } +// +// // Label hidden. +// $this->instance['display']['full']['label'] = 'hidden'; +// field_update_instance($this->instance); +// $entity->content = field_attach_view($entity_type, $entity); +// $output = drupal_render($entity->content); +// $variables = field_attach_preprocess($entity_type, $entity); +// $this->content = $output; +// $this->assertNoRaw($this->instance['label'], "Hidden label: label is not displayed."); +// $this->content = $variables[$variable]; +// $this->assertNoRaw($this->instance['label'], "Hidden label: label is not displayed (template variable)."); +// +// // Field hidden. +// $this->instance['display'] = array( +// 'full' => array( +// 'label' => 'above', +// 'type' => 'hidden', +// +// ), +// ); +// field_update_instance($this->instance); +// $entity->content = field_attach_view($entity_type, $entity); +// $output = drupal_render($entity->content); +// $variables = field_attach_preprocess($entity_type, $entity); +// $this->assertTrue(isset($variables[$variable]), "Hidden field: variable $variable is available in templates."); +// $this->content = $output; +// $this->assertNoRaw($this->instance['label'], "Hidden field: label is not displayed."); +// foreach ($values as $delta => $value) { +// $this->assertNoRaw($value['value'], "Hidden field: value $delta is not displayed."); +// } +// +// // Multiple formatter. +// $formatter_setting = $this->randomName(); +// $this->instance['display'] = array( +// 'full' => array( +// 'label' => 'above', +// 'type' => 'field_test_multiple', +// 'settings' => array( +// 'test_formatter_setting_multiple' => $formatter_setting, +// ) +// ), +// ); +// field_update_instance($this->instance); +// $entity->content = field_attach_view($entity_type, $entity); +// $output = drupal_render($entity->content); +// $variables = field_attach_preprocess($entity_type, $entity); +// $display = $formatter_setting; +// foreach ($values as $delta => $value) { +// $display .= "|$delta:{$value['value']}"; +// } +// $this->content = $output; +// $this->assertRaw($display, "Multiple formatter: all values are displayed, formatter settings are applied."); +// $this->content = $variables[$variable]; +// $this->assertRaw($display, "Multiple formatter: all values are displayed, formatter settings are applied (template variable)."); +// +// // TODO: +// // - check that the 'exclude' option works (if we keep it in core) +// // - check display order with several fields +// } +// +// function testFieldAttachDelete() { +// $entity_type = 'test_entity'; +// $rev[0] = field_test_create_stub_entity(0, 0, $this->instance['bundle']); +// +// // Create revision 0 +// $values = array(); +// for ($delta = 0; $delta < $this->field['cardinality']; $delta++) { +// $values[$delta]['value'] = mt_rand(1, 127); +// } +// $rev[0]->{$this->field_name} = $values; +// field_attach_insert($entity_type, $rev[0]); +// +// // Create revision 1 +// $rev[1] = field_test_create_stub_entity(0, 1, $this->instance['bundle']); +// $rev[1]->{$this->field_name} = $values; +// field_attach_update($entity_type, $rev[1]); +// +// // Create revision 2 +// $rev[2] = field_test_create_stub_entity(0, 2, $this->instance['bundle']); +// $rev[2]->{$this->field_name} = $values; +// field_attach_update($entity_type, $rev[2]); +// +// // Confirm each revision loads +// foreach (array_keys($rev) as $vid) { +// $read = field_test_create_stub_entity(0, $vid, $this->instance['bundle']); +// field_attach_load_revision($entity_type, array(0 => $read)); +// $this->assertEqual(count($read->{$this->field_name}), $this->field['cardinality'], "The test object revision $vid has {$this->field['cardinality']} values."); +// } +// +// // Delete revision 1, confirm the other two still load. +// field_attach_delete_revision($entity_type, $rev[1]); +// foreach (array(0, 2) as $vid) { +// $read = field_test_create_stub_entity(0, $vid, $this->instance['bundle']); +// field_attach_load_revision($entity_type, array(0 => $read)); +// $this->assertEqual(count($read->{$this->field_name}), $this->field['cardinality'], "The test object revision $vid has {$this->field['cardinality']} values."); +// } +// +// // Confirm the current revision still loads +// $read = field_test_create_stub_entity(0, 2, $this->instance['bundle']); +// field_attach_load($entity_type, array(0 => $read)); +// $this->assertEqual(count($read->{$this->field_name}), $this->field['cardinality'], "The test object current revision has {$this->field['cardinality']} values."); +// +// // Delete all field data, confirm nothing loads +// field_attach_delete($entity_type, $rev[2]); +// foreach (array(0, 1, 2) as $vid) { +// $read = field_test_create_stub_entity(0, $vid, $this->instance['bundle']); +// field_attach_load_revision($entity_type, array(0 => $read)); +// $this->assertIdentical($read->{$this->field_name}, array(), "The test object revision $vid is deleted."); +// } +// $read = field_test_create_stub_entity(0, 2, $this->instance['bundle']); +// field_attach_load($entity_type, array(0 => $read)); +// $this->assertIdentical($read->{$this->field_name}, array(), "The test object current revision is deleted."); +// } +// +// function testFieldAttachCreateRenameBundle() { +// // Create a new bundle. This has to be initiated by the module so that its +// // hook_fieldable_info() is consistent. +// $new_bundle = 'test_bundle_' . drupal_strtolower($this->randomName()); +// field_test_create_bundle($new_bundle, $this->randomName()); +// +// // Add an instance to that bundle. +// $this->instance['bundle'] = $new_bundle; +// field_create_instance($this->instance); +// +// // Save an object with data in the field. +// $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); +// $values = array(); +// for ($delta = 0; $delta < $this->field['cardinality']; $delta++) { +// $values[$delta]['value'] = mt_rand(1, 127); +// } +// $entity->{$this->field_name} = $values; +// $entity_type = 'test_entity'; +// field_attach_insert($entity_type, $entity); +// +// // Verify the field data is present on load. +// $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); +// field_attach_load($entity_type, array(0 => $entity)); +// $this->assertEqual(count($entity->{$this->field_name}), $this->field['cardinality'], "Data are retrieved for the new bundle"); +// +// // Rename the bundle. This has to be initiated by the module so that its +// // hook_fieldable_info() is consistent. +// $new_bundle = 'test_bundle_' . drupal_strtolower($this->randomName()); +// field_test_rename_bundle($this->instance['bundle'], $new_bundle); +// +// // Check that the instance definition has been updated. +// $this->instance = field_info_instance($this->field_name, $new_bundle); +// $this->assertIdentical($this->instance['bundle'], $new_bundle, "Bundle name has been updated in the instance."); +// +// // Verify the field data is present on load. +// $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); +// field_attach_load($entity_type, array(0 => $entity)); +// $this->assertEqual(count($entity->{$this->field_name}), $this->field['cardinality'], "Bundle name has been updated in the field storage"); +// } +// +// function testFieldAttachDeleteBundle() { +// // Create a new bundle. This has to be initiated by the module so that its +// // hook_fieldable_info() is consistent. +// $new_bundle = 'test_bundle_' . drupal_strtolower($this->randomName()); +// field_test_create_bundle($new_bundle, $this->randomName()); +// +// // Add an instance to that bundle. +// $this->instance['bundle'] = $new_bundle; +// field_create_instance($this->instance); +// +// // Create a second field for the test bundle +// $field_name = drupal_strtolower($this->randomName(). '_field_name'); +// $table = _field_sql_storage_tablename($field_name); +// $revision_table = _field_sql_storage_revision_tablename($field_name); +// $field = array('field_name' => $field_name, 'type' => 'test_field', 'cardinality' => 1); +// field_create_field($field); +// $instance = array( +// 'field_name' => $field_name, +// 'bundle' => $this->instance['bundle'], +// 'label' => $this->randomName(). '_label', +// 'description' => $this->randomName(). '_description', +// 'weight' => mt_rand(0, 127), +// // test_field has no instance settings +// 'widget' => array( +// 'type' => 'test_field_widget', +// 'settings' => array( +// 'size' => mt_rand(0, 255)))); +// field_create_instance($instance); +// +// // Save an object with data for both fields +// $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); +// $values = array(); +// for ($delta = 0; $delta < $this->field['cardinality']; $delta++) { +// $values[$delta]['value'] = mt_rand(1, 127); +// } +// $entity->{$this->field_name} = $values; +// $entity->{$field_name} = array(0 => array('value' => 99)); +// $entity_type = 'test_entity'; +// field_attach_insert($entity_type, $entity); +// +// // Verify the fields are present on load +// $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); +// field_attach_load($entity_type, array(0 => $entity)); +// $this->assertEqual(count($entity->{$this->field_name}), 4, "First field got loaded"); +// $this->assertEqual(count($entity->{$field_name}), 1, "Second field got loaded"); +// +// // Delete the bundle. This has to be initiated by the module so that its +// // hook_fieldable_info() is consistent. +// field_test_delete_bundle($this->instance['bundle']); +// +// // Verify no data gets loaded +// $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); +// field_attach_load($entity_type, array(0 => $entity)); +// $this->assertFalse(isset($entity->{$this->field_name}), "No data for first field"); +// $this->assertFalse(isset($entity->{$field_name}), "No data for second field"); +// +// // Verify that the instances are gone +// $this->assertFalse(field_read_instance($this->field_name, $this->instance['bundle']), "First field is deleted"); +// $this->assertFalse(field_read_instance($field_name, $instance['bundle']), "Second field is deleted"); +// } +// +// function testFieldAttachCache() { +// // Create a revision +// $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); +// $values = array(); +// for ($delta = 0; $delta < $this->field['cardinality']; $delta++) { +// $values[$delta]['value'] = mt_rand(1, 127); +// } +// $entity->{$this->field_name} = $values; +// +// $noncached_type = 'test_entity'; +// $cached_type = 'test_cacheable_entity'; +// +// // Non-cached type: +// $cid = "field:$noncached_type:0:0"; +// +// // Confirm no initial cache entry +// $this->assertFalse(cache_get($cid, 'cache_field'), 'Non-cached: no initial cache entry'); +// +// // Save, and confirm no cache entry +// field_attach_insert($noncached_type, $entity); +// $this->assertFalse(cache_get($cid, 'cache_field'), 'Non-cached: no cache entry on save'); +// +// // Load, and confirm no cache entry +// field_attach_load($noncached_type, array(0 => $entity)); +// $this->assertFalse(cache_get($cid, 'cache_field'), 'Non-cached: no cache entry on load'); +// +// // Cached type: +// $cid = "field:$cached_type:0:0"; +// +// // Confirm no initial cache entry +// $this->assertFalse(cache_get($cid, 'cache_field'), 'Cached: no initial cache entry'); +// +// // Save, and confirm no cache entry +// field_attach_insert($cached_type, $entity); +// $this->assertFalse(cache_get($cid, 'cache_field'), 'Cached: no cache entry on save'); +// +// // Load, and confirm cache entry +// field_attach_load($cached_type, array(0 => $entity)); +// $cache = cache_get($cid, 'cache_field'); +// $this->assertEqual($cache->data[$this->field_name], $values, 'Cached: correct cache entry on load'); +// +// // Delete, and confirm no cache entry +// field_attach_delete($cached_type, $entity); +// $this->assertFalse(cache_get($cid, 'cache_field'), 'Cached: no cache entry on save'); +// } +// +// // Verify that field_attach_validate() invokes the correct +// // hook_field_validate. +// function testFieldAttachValidate() { +// $entity_type = 'test_entity'; +// $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); +// +// // Set up values to generate errors +// $values = array(); +// for ($delta = 0; $delta < $this->field['cardinality']; $delta++) { +// $values[$delta]['value'] = -1; +// $values[$delta]['_error_element'] = 'field_error_' . $delta; +// } +// // Arrange for item 1 not to generate an error +// $values[1]['value'] = 1; +// $entity->{$this->field_name} = $values; +// +// try { +// field_attach_validate($entity_type, $entity); +// } +// catch (FieldValidationException $e) { +// $errors = $e->errors; +// } +// +// foreach ($values as $delta => $value) { +// if ($value['value'] != 1) { +// $this->assertIdentical($errors[$this->field_name][$delta][0]['error'], 'field_test_invalid', "Error set on value $delta"); +// $this->assertEqual(count($errors[$this->field_name][$delta]), 1, "Only one error set on value $delta"); +// unset($errors[$this->field_name][$delta]); +// } +// else { +// $this->assertFalse(isset($errors[$this->field_name][$delta]), "No error set on value $delta"); +// } +// } +// $this->assertEqual(count($errors[$this->field_name]), 0, 'No extraneous errors set'); +// } +// +// // Validate that FAPI elements are generated. This could be much +// // more thorough, but it does verify that the correct widgets show up. +// function testFieldAttachForm() { +// $entity_type = 'test_entity'; +// $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); +// +// $form = $form_state = array(); +// field_attach_form($entity_type, $entity, $form, $form_state); +// +// $this->assertEqual($form[$this->field_name]['#title'], $this->instance['label'], "Form title is {$this->instance['label']}"); +// for ($delta = 0; $delta < $this->field['cardinality']; $delta++) { +// // field_test_widget uses 'textfield' +// $this->assertEqual($form[$this->field_name][$delta]['value']['#type'], 'textfield', "Form delta $delta widget is textfield"); +// } +// } +// +// function testFieldAttachSubmit() { +// $entity_type = 'test_entity'; +// $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); +// +// // Build the form. +// $form = $form_state = array(); +// field_attach_form($entity_type, $entity, $form, $form_state); +// +// // Simulate incoming values. +// $values = array(); +// $weights = array(); +// for ($delta = 0; $delta < $this->field['cardinality']; $delta++) { +// $values[$delta]['value'] = mt_rand(1, 127); +// // Assign random weight. +// do { +// $weight = mt_rand(0, $this->field['cardinality']); +// } while (in_array($weight, $weights)); +// $weights[$delta] = $weight; +// $values[$delta]['_weight'] = $weight; +// } +// // Leave an empty value. 'field_test' fields are empty if empty(). +// $values[1]['value'] = 0; +// +// $form_state['values'] = array($this->field_name => $values); +// field_attach_submit($entity_type, $entity, $form, $form_state); +// +// asort($weights); +// $expected_values = array(); +// foreach ($weights as $key => $value) { +// if ($key != 1) { +// $expected_values[] = array('value' => $values[$key]['value']); +// } +// } +// $this->assertIdentical($entity->{$this->field_name}, $expected_values, 'Submit filters empty values'); +// } } class FieldInfoTestCase extends DrupalWebTestCase { 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 14 Apr 2009 19:09:49 -0000 @@ -163,32 +163,39 @@ * - TRUE: render the default field implementation of the field hook. * - FALSE: render the field module's implementation of the field hook. */ -function _field_invoke($op, $obj_type, &$object, &$a = NULL, &$b = NULL, $default = FALSE) { +function _field_invoke($op, $obj_type, &$object, &$a = NULL, &$b = NULL, $options = array()) { + $default_options = array( + 'default' => FALSE, + ); + $options += $default_options; + list(, , $bundle) = field_attach_extract_ids($obj_type, $object); $instances = field_info_instances($bundle); $return = array(); foreach ($instances as $instance) { $field_name = $instance['field_name']; - $field = field_info_field($field_name); - $items = isset($object->$field_name) ? $object->$field_name : array(); - - $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); + if (empty($options['field_name']) || $options['field_name'] == $field_name) { + $field = field_info_field($field_name); + $items = isset($object->$field_name) ? $object->$field_name : array(); + + $function = $options['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; + } } - else if (isset($result)) { - $return[] = $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)) { + $object->$field_name = $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; @@ -197,8 +204,9 @@ /** * Invoke field.module's version of a field hook. */ -function _field_invoke_default($op, $obj_type, &$object, &$a = NULL, &$b = NULL) { - return _field_invoke($op, $obj_type, $object, $a, $b, TRUE); +function _field_invoke_default($op, $obj_type, &$object, &$a = NULL, &$b = NULL, $options = array()) { + $options['default'] = TRUE; + return _field_invoke($op, $obj_type, $object, $a, $b, $options); } /** @@ -609,6 +617,119 @@ } /** + * Return all object types and ids containing field data items matching a query. + * + * Note that field values that come out of a regular field_attach_load() call + * go through hook_field_load() and hook_field_attach_load() invocations, which + * might add to or affect the raw stored values. The conditions specified in + * field_attach_query() and field_attach_query_revisions() only apply to the + * stored values. + * + * @param $field_name + * The name of the field to query. + * @param $conditions + * An array of query conditions. Each condition is a numerically indexed + * array, in the form array(column, value, operator). + * Supported columns: + * - any of the columns for $field_name's field type: condition on field + * value, + * - 'type': condition on object type (e.g. 'node', 'user'...), + * - 'bundle': condition on object bundle (e.g. node type), + * - 'entity_id': condition on object id (e.g node nid, user uid...), + * The field_attach_query_revisions() function additionally supports: + * - 'revision_id': condition on object revision id (e.g node vid). + * Not all storage engines are required to support queries on all column types. + * TODO: how can a module providing 'X as field' safely build on this, then ? + * Supported operators: + * - '=', '!=', '>', '>=', '<', '<=', 'starts_with', 'contains': + * these operators expect the value as literal of the same type as the + * column, + * - 'IN': this operator expects the value an array of literals of the same + * type as the column. + * The operator can be ommitted, and will default to 'IN' if the value is + * an array, or to '=' otherwise. + * Example values for $conditions: + * @code + * array( + * array('type', 'node'), + * ); + * array( + * array('bundle', array('article', 'page')), + * array('value', 12, '>'), + * ); + * @endcode + * @param $result_format + * - FIELD_QUERY_RETURN_VALUES (default): return the values for the field. + * - FIELD_QUERY_RETURN_IDS: return the ids of the objects matching the + * conditions. + * @param $age + * Internal use only. Use field_attach_query_revisions() instead of passing + * FIELD_LOAD_REVISION. + * - FIELD_LOAD_CURRENT (default): query the most recent revisions for all + * objects + * - FIELD_LOAD_REVISION: query all revisions. + * @return + * An array keyed by object type (e.g. 'node', 'user'...), then by object id, + * and whose values depend on the $result_format parameter: + * - FIELD_QUERY_RETURN_VALUES: a pseudo-object with values for the + * $field_name field. This only includes values matching the conditions, + * and thus might not contain all actual values, or in the actual delta + * sequence. + * The pseudo-objects only include properties that the Field API knows + * about: bundle, id, revision id, and field values (no node title, user + * name...). + * - FIELD_QUERY_RETURN_IDS: the object id. + */ +function _field_attach_query($field_name, $conditions, $result_format = FIELD_QUERY_RETURN_VALUES, $age = FIELD_LOAD_CURRENT) { + // Give a chance to 3rd party modules that bypass the storage engine to + // handle the query. + $skip_field = FALSE; + foreach (module_implements('field_attach_pre_query') as $module) { + $function = $module . '_field_attach_pre_query'; + $results = $function($field_name, $conditions, $result_format, $skip_field); + } + // If the request hasn't been handled, let the storage engine handle it. + if (!$skip_field) { + $results = module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'field_storage_query', $field_name, $conditions, $result_format, $age); + } + + // TODO: what about field types that handle their own storage, + // or add to the field values fetching from another table (filefield) ? + // should we provide a hook_field_query() to let them handle that ? + + if ($result_format == FIELD_QUERY_RETURN_VALUES) { + foreach ($results as $obj_type => $type_results) { + foreach ($type_results as $id => $pseudo_object) { + // Invoke hook_field_load(). + // TODO: revisit when hook_field_load is made multiple. + $a = $b = NULL; + $custom_additions = _field_invoke('load', $obj_type, $pseudo_object, $a, $b, array('field_name' => $field_name)); + foreach ($custom_additions as $key => $value) { + $pseudo_object->$key = $value; + } + + // Invoke hook_field_attach_load(). + foreach (module_implements('field_attach_load') as $module) { + $function = $module . '_field_attach_load'; + $function($obj_type, $pseudo_object); + } + + $results[$obj_type][$id] = $pseudo_object; + } + } + } + + return $results; +} + +/** + * TODO + */ +function _field_attach_query_revisions($field_name, $conditions, $result_format = FIELD_QUERY_RETURN_VALUES) { + return field_attach_query($field_name, $conditions, $result_format, FIELD_LOAD_REVISION); +} + +/** * Generate and return a structured content array tree suitable for * drupal_render() for all of the fields on an object. The format of * each field's rendered content depends on the display formatter and @@ -772,5 +893,34 @@ } /** + * Helper function to assemble an object structure with initial ids. + * + * This function can be seen as reciprocal to field_attach_extract_ids() + * + * @param $obj_type + * The type of $object; e.g. 'node' or 'user'. + * @param $ids + * A numerically indexed array, as returned by field_attach_extract_ids(), + * containing these elements: + * 0: primary id of the object + * 1: revision id of the object, or NULL if $obj_type is not versioned + * 2: bundle name of the object + * @return + * An $object structure, initialized with the ids provided. + */ +function _field_attach_create_stub_object($obj_type, $ids) { + $object = new stdClass(); + $info = field_info_fieldable_types($obj_type); + $object->{$info['id key']} = $ids[0]; + if (isset($info['revision key']) && !is_null($ids[1])) { + $object->{$info['revision key']} = $ids[1]; + } + if ($info['bundle key']) { + $object->{$info['bundle key']} = $ids[2]; + } + return $object; +} + +/** * @autoload} End of "@autoload field_attach" */ Index: modules/field/field.api.php =================================================================== RCS file: /cvs/drupal/drupal/modules/field/field.api.php,v retrieving revision 1.7 diff -u -r1.7 field.api.php --- modules/field/field.api.php 13 Apr 2009 05:18:17 -0000 1.7 +++ modules/field/field.api.php 14 Apr 2009 19:09:49 -0000 @@ -192,6 +192,10 @@ * The type of $object. * @param $object * The object for the operation. + * Note that this might not be a full-fledged 'object'. When invoked through + * field_attach_query(), the $object will only include properties that the + * Field API knows about : bundle, id, revision id, and field values (no node + * title, user name...). * @param $field * The field structure for the operation. * @param $instance @@ -477,6 +481,12 @@ * This hook is invoked after the field module has performed the operation. * * See field_attach_load() for details and arguments. + * + * Note that $object might not be a full-fledged 'object'. When invoked through + * field_attach_query(), the $object will only include properties that the Field + * API knows about : bundle, id, revision id, and field values (no node title, + * user name...) + * * TODO: Currently, this hook only accepts a single object a time. */ function hook_field_attach_load($obj_type, $object) { @@ -553,6 +563,12 @@ } /** + * TODO + */ +function hook_field_attach_pre_query($field_name, $conditions, $result_format, $skip_field) { +} + +/** * Act on field_attach_delete. * * This hook is invoked after the field module has performed the operation. 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 14 Apr 2009 19:09:50 -0000 @@ -78,6 +78,14 @@ */ define('FIELD_LOAD_REVISION', 'FIELD_LOAD_REVISION'); +/** + * TODO + */ +define('FIELD_QUERY_RETURN_VALUES', 'FIELD_QUERY_RETURN_VALUES'); +/** + * TODO + */ +define('FIELD_QUERY_RETURN_IDS', 'FIELD_QUERY_RETURN_IDS'); /** * Base class for all exceptions thrown by Field API functions. Index: modules/field/field.autoload.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/field/field.autoload.inc,v retrieving revision 1.6 diff -u -r1.6 field.autoload.inc --- modules/field/field.autoload.inc 26 Mar 2009 13:31:24 -0000 1.6 +++ modules/field/field.autoload.inc 14 Apr 2009 19:09:50 -0000 @@ -248,6 +248,26 @@ } /** + * TODO + * + * This function is an autoloader for _field_attach_query() in modules/field/field.attach.inc. + */ +function field_attach_query($field_name, $conditions, $result_format = FIELD_QUERY_RETURN_VALUES, $age = FIELD_LOAD_CURRENT) { + require_once DRUPAL_ROOT . '/modules/field/field.attach.inc'; + return _field_attach_query($field_name, $conditions, $result_format, $age); +} + +/** + * TODO + * + * This function is an autoloader for _field_attach_query_revisions() in modules/field/field.attach.inc. + */ +function field_attach_query_revisions($field_name, $conditions, $result_format = FIELD_QUERY_RETURN_VALUES) { + require_once DRUPAL_ROOT . '/modules/field/field.attach.inc'; + return _field_attach_query_revisions($field_name, $conditions, $result_format); +} + +/** * Generate and return a structured content array tree suitable for * drupal_render() for all of the fields on an object. The format of * each field's rendered content depends on the display formatter and @@ -370,6 +390,29 @@ } /** + * Helper function to assemble an object structure with initial ids. + * + * This function can be seen as reciprocal to field_attach_extract_ids(). + * + * @param $obj_type + * The type of $object; e.g. 'node' or 'user'. + * @param $ids + * A numerically indexed array, as returned by field_attach_extract_ids(), + * containing these elements: + * 0: primary id of the object + * 1: revision id of the object, or NULL if $obj_type is not versioned + * 2: bundle name of the object + * @return + * An $object structure, initialized with the ids provided. + * + * This function is an autoloader for _field_attach_create_stub_object() in modules/field/field.attach.inc. + */ +function field_attach_create_stub_object($object_type, $ids) { + require_once DRUPAL_ROOT . '/modules/field/field.attach.inc'; + return _field_attach_create_stub_object($object_type, $ids); +} + +/** * @} End of "field_attach" */ 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 14 Apr 2009 19:09:51 -0000 @@ -214,7 +214,8 @@ // 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)}; + $column_name = _field_sql_storage_columnname($field_name, $column); + $item[$column] = $row->$column_name; } // Add the item to the field values for the entity. @@ -349,6 +350,116 @@ } } + +/** + * Implementation of hook_field_storage_query(). + * + * TODO : Doc should go in field.api.php doc. + * + * @param $field_name + * The name of the field to query. + * @param $conditions + * An array of query conditions on the field columns. Supported columns: + * 'type', 'bundle', 'entity_id', 'revision_id', plus field columns. + * @param $result_format + * TODO + * @param $age + * - FIELD_LOAD_CURRENT: query the most recent revisions for all objects + * - FIELD_LOAD_REVISION: query all revisions. + * @return + * TODO + */ +function field_sql_storage_field_storage_query($field_name, $conditions, $result_format, $age) { + $load_values = $result_format == FIELD_QUERY_RETURN_VALUES; + $load_current = $age == FIELD_LOAD_CURRENT; + $table = $load_current ? _field_sql_storage_tablename($field_name) : _field_sql_storage_revision_tablename($field_name); + + $field = field_info_field($field_name); + $field_columns = array_keys($field['columns']); + + // Build the query. + $query = db_select($table, 't'); + $query->join('field_config_entity_type', 'e', 't.etid = e.etid'); + $query->fields('e', array('type')) + ->condition('deleted', 0) + ->orderBy('delta'); + // Add fields, depending on the return format. + if ($load_values) { + $query->fields('t'); + } + else { + $query->fields('t', array('entity_id', 'revision_id')); + } + // Add conditions. + foreach ($conditions as $condition) { + // Prevent warning if $operator is implicit. + @list($column, $value, $operator) = $condition; + // Translate operator. + switch ($operator) { + case NULL: + $operator = is_array($value) ? 'IN' : '='; + break; + + case 'starts_with': + $operator = 'LIKE'; + $value .= '%'; + break; + + case 'contains': + $operator = 'LIKE'; + $value = "%$value%"; + break; + + } + // Translate field columns into prefixed db columns. + if (in_array($column, $field_columns)) { + $column = _field_sql_storage_columnname($field_name, $column); + } + $query->condition($column, $value, $operator); + } + + $results = $query->execute(); + + $return = array(); + $delta_count = array(); + foreach ($results as $row) { + // If querying all revisions and the entity type has revisions, we need to + // key the results by revision_ids. + $entity_type = field_info_fieldable_types($row->type); + $id = ($load_current || empty($entity_type['revision key'])) ? $row->entity_id : $row->revision_id; + + if ($load_values) { + // Populate actual field values. + if (!isset($delta_count[$row->type][$id])) { + $delta_count[$row->type][$id] = 0; + } + if ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED || $delta_count[$row->type][$id] < $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) { + $column_name = _field_sql_storage_columnname($field_name, $column); + $item[$column] = $row->$column_name; + } + + // Initialize the 'pseudo object' if needed. + if (!isset($return[$row->type][$id])) { + $return[$row->type][$id] = field_attach_create_stub_object($row->type, array($row->entity_id, $row->revision_id, $row->bundle)); + } + // Add the item to the field values for the entity. + $return[$row->type][$id]->{$field_name}[] = $item; + $delta_count[$row->type][$id]++; + } + } + else { + // Simply return the list of selected ids. + $return[$row->type][$id] = $id; + } + } + + return $return; +} + /** * Implementation of hook_field_storage_delete_instance(). *