summaryrefslogtreecommitdiff
path: root/modules/field
diff options
context:
space:
mode:
Diffstat (limited to 'modules/field')
-rw-r--r--modules/field/field.api.php30
-rw-r--r--modules/field/field.attach.inc18
-rw-r--r--modules/field/field.crud.inc18
-rw-r--r--modules/field/field.info.inc11
-rw-r--r--modules/field/field.multilingual.inc3
-rw-r--r--modules/field/tests/field.test120
-rw-r--r--modules/field/tests/field_test.entity.inc4
-rw-r--r--modules/field/tests/field_test.field.inc40
-rw-r--r--modules/field/tests/field_test.module59
9 files changed, 220 insertions, 83 deletions
diff --git a/modules/field/field.api.php b/modules/field/field.api.php
index 74eae62ab..329cf16d1 100644
--- a/modules/field/field.api.php
+++ b/modules/field/field.api.php
@@ -1,7 +1,7 @@
<?php
/**
- * @ingroup field_fieldable_type
+ * @ingroup hooks
* @{
*/
@@ -74,17 +74,13 @@ function hook_field_extra_fields() {
function hook_field_extra_fields_alter(&$info) {
// Force node title to always be at the top of the list by default.
foreach (node_type_get_types() as $bundle) {
- if (isset($info['node'][$bundle->type]['title'])) {
- $info['node'][$bundle->type]['title']['weight'] = -20;
+ if (isset($info['node'][$bundle->type]['form']['title'])) {
+ $info['node'][$bundle->type]['form']['title']['weight'] = -20;
}
}
}
/**
- * @} End of "ingroup field_fieldable_type"
- */
-
-/**
* @defgroup field_types Field Types API
* @{
* Define field types, widget types, display formatter types, storage types.
@@ -119,6 +115,9 @@ function hook_field_extra_fields_alter(&$info) {
*
* A third kind of pluggable handlers, storage backends, is defined by the
* @link field_storage Field Storage API @endlink.
+ *
+ * See @link field Field API @endlink for information about the other parts of
+ * the Field API.
*/
/**
@@ -706,6 +705,7 @@ function hook_field_is_empty($item, $field) {
* @see hook_field_widget_form_alter()
* @see hook_field_widget_WIDGET_TYPE_form_alter()
* @see hook_field_widget_error()
+ * @see hook_field_widget_settings_form()
*/
function hook_field_widget_info() {
return array(
@@ -1505,10 +1505,6 @@ function hook_field_attach_delete_bundle($entity_type, $bundle, $instances) {
* @} End of "ingroup field_attach"
*/
-/**********************************************************************
- * Field Storage API
- **********************************************************************/
-
/**
* @ingroup field_storage
* @{
@@ -2346,10 +2342,6 @@ function hook_field_widget_properties_ENTITY_TYPE_alter(&$widget, $context) {
* @} End of "ingroup field_storage"
*/
-/**********************************************************************
- * Field CRUD API
- **********************************************************************/
-
/**
* @ingroup field_crud
* @{
@@ -2603,10 +2595,6 @@ function hook_field_storage_purge($entity_type, $entity, $field, $instance) {
* @} End of "ingroup field_crud"
*/
-/**********************************************************************
- * TODO: I'm not sure where these belong yet.
- **********************************************************************/
-
/**
* Determine whether the user has access to a given field.
*
@@ -2633,3 +2621,7 @@ function hook_field_access($op, $field, $entity_type, $entity, $account) {
}
return TRUE;
}
+
+/**
+ * @} End of "ingroup hooks"
+ */
diff --git a/modules/field/field.attach.inc b/modules/field/field.attach.inc
index 2419201de..bd2934b48 100644
--- a/modules/field/field.attach.inc
+++ b/modules/field/field.attach.inc
@@ -44,6 +44,9 @@ class FieldValidationException extends FieldException {
*
* Each field defines which storage backend it uses. The Drupal system variable
* 'field_storage_default' identifies the storage backend used by default.
+ *
+ * See @link field Field API @endlink for information about the other parts of
+ * the Field API.
*/
/**
@@ -117,6 +120,12 @@ define('FIELD_STORAGE_INSERT', 'insert');
* The pre-operation hooks do not make the Field Storage API irrelevant. The
* Field Storage API is essentially the "fallback mechanism" for any fields
* that aren't being intercepted explicitly by pre-operation hooks.
+ *
+ * @link field_language Field Language API @endlink provides information about
+ * the structure of field objects.
+ *
+ * See @link field Field API @endlink for information about the other parts of
+ * the Field API.
*/
/**
@@ -185,8 +194,10 @@ function _field_invoke($op, $entity_type, $entity, &$a = NULL, &$b = NULL, $opti
// Iterate through the instances and collect results.
$return = array();
foreach ($instances as $instance) {
- $field_name = $instance['field_name'];
- $field = field_info_field($field_name);
+ // field_info_field() is not available for deleted fields, so use
+ // field_info_field_by_id().
+ $field = field_info_field_by_id($instance['field_id']);
+ $field_name = $field['field_name'];
$function = $options['default'] ? 'field_default_' . $op : $field['module'] . '_field_' . $op;
if (function_exists($function)) {
// Determine the list of languages to iterate on.
@@ -694,7 +705,8 @@ function field_attach_load($entity_type, $entities, $age = FIELD_LOAD_CURRENT, $
}
// Invoke field-type module's hook_field_load().
- _field_invoke_multiple('load', $entity_type, $queried_entities, $age, $options);
+ $null = NULL;
+ _field_invoke_multiple('load', $entity_type, $queried_entities, $age, $null, $options);
// Invoke hook_field_attach_load(): let other modules act on loading the
// entitiy.
diff --git a/modules/field/field.crud.inc b/modules/field/field.crud.inc
index e34c0c528..6df32352b 100644
--- a/modules/field/field.crud.inc
+++ b/modules/field/field.crud.inc
@@ -15,6 +15,9 @@
*
* The Field CRUD API uses
* @link field Field API data structures @endlink.
+ *
+ * See @link field Field API @endlink for information about the other parts of
+ * the Field API.
*/
/**
@@ -42,10 +45,11 @@
* system variable.
* - settings: each omitted setting is given the default value specified in
* hook_field_storage_info().
+ *
* @return
* The $field array with the id property filled in.
- * @throw
- * FieldException
+ *
+ * @throws FieldException
*
* See: @link field Field API data structures @endlink.
*/
@@ -442,8 +446,8 @@ function field_delete_field($field_name) {
*
* @return
* The $instance array with the id property filled in.
- * @throw
- * FieldException
+ *
+ * @throws FieldException
*
* See: @link field Field API data structures @endlink.
*/
@@ -510,8 +514,7 @@ function field_create_instance($instance) {
* properties specified in $instance overwrite the existing values for
* the instance.
*
- * @throw
- * FieldException
+ * @throws FieldException
*
* @see field_create_instance()
*/
@@ -824,6 +827,9 @@ function field_delete_instance($instance, $field_cleanup = TRUE) {
* ),
* );
* @endcode
+ *
+ * See @link field Field API @endlink for information about the other parts of
+ * the Field API.
*/
/**
diff --git a/modules/field/field.info.inc b/modules/field/field.info.inc
index 6b172dd34..3b36c0355 100644
--- a/modules/field/field.info.inc
+++ b/modules/field/field.info.inc
@@ -13,6 +13,9 @@
* The Field Info API exposes information about field types, fields,
* instances, bundles, widget types, display formatters, behaviors,
* and settings defined by or with the Field API.
+ *
+ * See @link field Field API @endlink for information about the other parts of
+ * the Field API.
*/
/**
@@ -24,6 +27,7 @@
*/
function field_info_cache_clear() {
drupal_static_reset('field_view_mode_settings');
+ drupal_static_reset('field_available_languages');
// @todo: Remove this when field_attach_*_bundle() bundle management
// functions are moved to the entity API.
@@ -602,8 +606,9 @@ function field_info_fields() {
*
* @param $field_name
* The name of the field to retrieve. $field_name can only refer to a
- * non-deleted, active field. Use field_read_fields() to retrieve information
- * on deleted or inactive fields.
+ * non-deleted, active field. For deleted fields, use
+ * field_info_field_by_id(). To retrieve information about inactive fields,
+ * use field_read_fields().
*
* @return
* The field array, as returned by field_read_fields(), with an
@@ -624,7 +629,7 @@ function field_info_field($field_name) {
*
* @param $field_id
* The id of the field to retrieve. $field_id can refer to a
- * deleted field.
+ * deleted field, but not an inactive one.
*
* @return
* The field array, as returned by field_read_fields(), with an
diff --git a/modules/field/field.multilingual.inc b/modules/field/field.multilingual.inc
index 5373d9708..4b4f01d7d 100644
--- a/modules/field/field.multilingual.inc
+++ b/modules/field/field.multilingual.inc
@@ -57,6 +57,9 @@
* even disabled by modules implementing hook_field_language_alter(), making
* it possible to choose the first approach. The display language for each
* field is returned by field_language().
+ *
+ * See @link field Field API @endlink for information about the other parts of
+ * the Field API.
*/
/**
diff --git a/modules/field/tests/field.test b/modules/field/tests/field.test
index 669fc37cf..657f1f364 100644
--- a/modules/field/tests/field.test
+++ b/modules/field/tests/field.test
@@ -72,12 +72,13 @@ class FieldTestCase extends DrupalWebTestCase {
}
class FieldAttachTestCase extends FieldTestCase {
- function setUp($modules = array()) {
+ function setUp() {
// Since this is a base class for many test cases, support the same
// flexibility that DrupalWebTestCase::setUp() has for the modules to be
// passed in as either an array or a variable number of string arguments.
- if (!is_array($modules)) {
- $modules = func_get_args();
+ $modules = func_get_args();
+ if (isset($modules[0]) && is_array($modules[0])) {
+ $modules = $modules[0];
}
if (!in_array('field_test', $modules)) {
$modules[] = 'field_test';
@@ -2674,7 +2675,7 @@ class FieldTranslationsTestCase extends FieldTestCase {
// Test field_available_languages() behavior for untranslatable fields.
$this->field['translatable'] = FALSE;
- $this->field_name = $this->field['field_name'] = $this->instance['field_name'] = drupal_strtolower($this->randomName() . '_field_name');
+ field_update_field($this->field);
$available_languages = field_available_languages($this->entity_type, $this->field);
$this->assertTrue(count($available_languages) == 1 && $available_languages[0] === LANGUAGE_NONE, t('For untranslatable fields only LANGUAGE_NONE is available.'));
}
@@ -2989,22 +2990,54 @@ class FieldBulkDeleteTestCase extends FieldTestCase {
*/
function _generateStubEntities($entity_type, $entities, $field_name = NULL) {
$stubs = array();
- foreach ($entities as $entity) {
+ foreach ($entities as $id => $entity) {
$stub = entity_create_stub_entity($entity_type, entity_extract_ids($entity_type, $entity));
if (isset($field_name)) {
$stub->{$field_name} = $entity->{$field_name};
}
- $stubs[] = $stub;
+ $stubs[$id] = $stub;
}
return $stubs;
}
+ /**
+ * Tests that the expected hooks have been invoked on the expected entities.
+ *
+ * @param $expected_hooks
+ * An array keyed by hook name, with one entry per expected invocation.
+ * Each entry is the value of the "$entity" parameter the hook is expected
+ * to have been passed.
+ * @param $actual_hooks
+ * The array of actual hook invocations recorded by field_test_memorize().
+ */
+ function checkHooksInvocations($expected_hooks, $actual_hooks) {
+ foreach ($expected_hooks as $hook => $invocations) {
+ $actual_invocations = $actual_hooks[$hook];
+
+ // Check that the number of invocations is correct.
+ $this->assertEqual(count($actual_invocations), count($invocations), "$hook() was called the expected number of times.");
+
+ // Check that the hook was called for each expected argument.
+ foreach ($invocations as $argument) {
+ $found = FALSE;
+ foreach ($actual_invocations as $actual_arguments) {
+ if ($actual_arguments[1] == $argument) {
+ $found = TRUE;
+ break;
+ }
+ }
+ $this->assertTrue($found, "$hook() was called on expected argument");
+ }
+ }
+ }
+
function setUp() {
parent::setUp('field_test');
- // Clean up data from previous test cases.
$this->fields = array();
$this->instances = array();
+ $this->entities = array();
+ $this->entities_by_bundles = array();
// Create two bundles.
$this->bundles = array('bb_1' => 'bb_1', 'bb_2' => 'bb_2');
@@ -3040,7 +3073,10 @@ class FieldBulkDeleteTestCase extends FieldTestCase {
foreach ($this->fields as $field) {
$entity->{$field['field_name']}[LANGUAGE_NONE] = $this->_generateTestFieldValues($field['cardinality']);
}
+
$this->entities[$id] = $entity;
+ // Also keep track of the entities per bundle.
+ $this->entities_by_bundles[$bundle][$id] = $entity;
field_attach_insert($this->entity_type, $entity);
$id++;
}
@@ -3105,6 +3141,7 @@ class FieldBulkDeleteTestCase extends FieldTestCase {
* instance is deleted.
*/
function testPurgeInstance() {
+ // Start recording hook invocations.
field_test_memorize();
$bundle = reset($this->bundles);
@@ -3119,7 +3156,7 @@ class FieldBulkDeleteTestCase extends FieldTestCase {
$this->assertEqual(count($mem), 0, 'No field hooks were called');
$batch_size = 2;
- for ($count = 8; $count >= 0; $count -= 2) {
+ for ($count = 8; $count >= 0; $count -= $batch_size) {
// Purge two entities.
field_purge_batch($batch_size);
@@ -3133,19 +3170,21 @@ class FieldBulkDeleteTestCase extends FieldTestCase {
$this->assertEqual($count ? count($found['test_entity']) : count($found), $count, 'Correct number of entities found after purging 2');
}
- // hook_field_delete() was called on a pseudo-entity for each entity. Each
- // pseudo entity has a $field property that matches the original entity,
- // but no others.
- $mem = field_test_memorize();
- $this->assertEqual(count($mem['field_test_field_delete']), 10, 'hook_field_delete was called for the right number of entities');
- $stubs = $this->_generateStubEntities($this->entity_type, $this->entities, $field['field_name']);
- $count = count($stubs);
- foreach ($mem['field_test_field_delete'] as $args) {
- $entity = $args[1];
- $this->assertEqual($stubs[$entity->ftid], $entity, 'hook_field_delete() called with the correct stub');
- unset($stubs[$entity->ftid]);
+ // Check hooks invocations.
+ // - hook_field_load() (multiple hook) should have been called on all
+ // entities by pairs of two.
+ // - hook_field_delete() should have been called once for each entity in the
+ // bundle.
+ $actual_hooks = field_test_memorize();
+ $hooks = array();
+ $stubs = $this->_generateStubEntities($this->entity_type, $this->entities_by_bundles[$bundle], $field['field_name']);
+ foreach (array_chunk($stubs, $batch_size, TRUE) as $chunk) {
+ $hooks['field_test_field_load'][] = $chunk;
}
- $this->assertEqual(count($stubs), $count-10, 'hook_field_delete was called with each entity once');
+ foreach ($stubs as $stub) {
+ $hooks['field_test_field_delete'][] = $stub;
+ }
+ $this->checkHooksInvocations($hooks, $actual_hooks);
// The instance still exists, deleted.
$instances = field_read_instances(array('field_id' => $field['id'], 'deleted' => 1), array('include_deleted' => 1, 'include_inactive' => 1));
@@ -3168,15 +3207,37 @@ class FieldBulkDeleteTestCase extends FieldTestCase {
* instances are deleted and purged.
*/
function testPurgeField() {
+ // Start recording hook invocations.
+ field_test_memorize();
+
$field = reset($this->fields);
// Delete the first instance.
- $instance = field_info_instance($this->entity_type, $field['field_name'], 'bb_1');
+ $bundle = reset($this->bundles);
+ $instance = field_info_instance($this->entity_type, $field['field_name'], $bundle);
field_delete_instance($instance);
+ // Assert that hook_field_delete() was not called yet.
+ $mem = field_test_memorize();
+ $this->assertEqual(count($mem), 0, 'No field hooks were called.');
+
// Purge the data.
field_purge_batch(10);
+ // Check hooks invocations.
+ // - hook_field_load() (multiple hook) should have been called once, for all
+ // entities in the bundle.
+ // - hook_field_delete() should have been called once for each entity in the
+ // bundle.
+ $actual_hooks = field_test_memorize();
+ $hooks = array();
+ $stubs = $this->_generateStubEntities($this->entity_type, $this->entities_by_bundles[$bundle], $field['field_name']);
+ $hooks['field_test_field_load'][] = $stubs;
+ foreach ($stubs as $stub) {
+ $hooks['field_test_field_delete'][] = $stub;
+ }
+ $this->checkHooksInvocations($hooks, $actual_hooks);
+
// Purge again to purge the instance.
field_purge_batch(0);
@@ -3185,12 +3246,27 @@ class FieldBulkDeleteTestCase extends FieldTestCase {
$this->assertTrue(isset($fields[$field['id']]) && !$fields[$field['id']]['deleted'], 'The field exists and is not deleted');
// Delete the second instance.
- $instance = field_info_instance($this->entity_type, $field['field_name'], 'bb_2');
+ $bundle = next($this->bundles);
+ $instance = field_info_instance($this->entity_type, $field['field_name'], $bundle);
field_delete_instance($instance);
+ // Assert that hook_field_delete() was not called yet.
+ $mem = field_test_memorize();
+ $this->assertEqual(count($mem), 0, 'No field hooks were called.');
+
// Purge the data.
field_purge_batch(10);
+ // Check hooks invocations (same as above, for the 2nd bundle).
+ $actual_hooks = field_test_memorize();
+ $hooks = array();
+ $stubs = $this->_generateStubEntities($this->entity_type, $this->entities_by_bundles[$bundle], $field['field_name']);
+ $hooks['field_test_field_load'][] = $stubs;
+ foreach ($stubs as $stub) {
+ $hooks['field_test_field_delete'][] = $stub;
+ }
+ $this->checkHooksInvocations($hooks, $actual_hooks);
+
// The field still exists, deleted.
$fields = field_read_fields(array('id' => $field['id']), array('include_deleted' => 1));
$this->assertTrue(isset($fields[$field['id']]) && $fields[$field['id']]['deleted'], 'The field exists and is deleted');
diff --git a/modules/field/tests/field_test.entity.inc b/modules/field/tests/field_test.entity.inc
index b7c70a677..52ed3fcd0 100644
--- a/modules/field/tests/field_test.entity.inc
+++ b/modules/field/tests/field_test.entity.inc
@@ -58,7 +58,7 @@ function field_test_entity_info() {
'id' => 'ftid',
'bundle' => 'fttype',
),
- 'bundles' => array('bundle1' => array('label' => 'Bundle1'), 'bundle2' => array('label' => 'Bundle2')),
+ 'bundles' => array('bundle1' => array('label' => 'Bundle1'), 'bundle2' => array('label' => 'Bundle2')) + $bundles,
'view modes' => $test_entity_modes,
),
// In this case, the bundle key is not stored in the database.
@@ -72,7 +72,7 @@ function field_test_entity_info() {
'id' => 'ftid',
'bundle' => 'fttype',
),
- 'bundles' => array('test_entity_2' => array('label' => 'Test entity 2')),
+ 'bundles' => array('test_entity_2' => array('label' => 'Test entity 2')) + $bundles,
'view modes' => $test_entity_modes,
),
// @see EntityPropertiesTestCase::testEntityLabel()
diff --git a/modules/field/tests/field_test.field.inc b/modules/field/tests/field_test.field.inc
index b8a2939d6..cc76a998d 100644
--- a/modules/field/tests/field_test.field.inc
+++ b/modules/field/tests/field_test.field.inc
@@ -58,6 +58,9 @@ function field_test_field_update_forbid($field, $prior_field, $has_data) {
* Implements hook_field_load().
*/
function field_test_field_load($entity_type, $entities, $field, $instances, $langcode, &$items, $age) {
+ $args = func_get_args();
+ field_test_memorize(__FUNCTION__, $args);
+
foreach ($items as $id => $item) {
// To keep the test non-intrusive, only act for instances with the
// test_hook_field_load setting explicitly set to TRUE.
@@ -73,12 +76,39 @@ function field_test_field_load($entity_type, $entities, $field, $instances, $lan
}
/**
+ * Implements hook_field_insert().
+ */
+function field_test_field_insert($entity_type, $entity, $field, $instance, $items) {
+ $args = func_get_args();
+ field_test_memorize(__FUNCTION__, $args);
+}
+
+/**
+ * Implements hook_field_update().
+ */
+function field_test_field_update($entity_type, $entity, $field, $instance, $items) {
+ $args = func_get_args();
+ field_test_memorize(__FUNCTION__, $args);
+}
+
+/**
+ * Implements hook_field_delete().
+ */
+function field_test_field_delete($entity_type, $entity, $field, $instance, $items) {
+ $args = func_get_args();
+ field_test_memorize(__FUNCTION__, $args);
+}
+
+/**
* Implements hook_field_validate().
*
* Possible error codes:
* - 'field_test_invalid': The value is invalid.
*/
function field_test_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) {
+ $args = func_get_args();
+ field_test_memorize(__FUNCTION__, $args);
+
foreach ($items as $delta => $item) {
if ($item['value'] == -1) {
$errors[$field['field_name']][$langcode][$delta][] = array(
@@ -350,11 +380,13 @@ function field_test_field_formatter_view($entity_type, $entity, $field, $instanc
break;
case 'field_test_multiple':
- $array = array();
- foreach ($items as $delta => $item) {
- $array[] = $delta . ':' . $item['value'];
+ if (!empty($items)) {
+ $array = array();
+ foreach ($items as $delta => $item) {
+ $array[] = $delta . ':' . $item['value'];
+ }
+ $element[0] = array('#markup' => $settings['test_formatter_setting_multiple'] . '|' . implode('|', $array));
}
- $element[0] = array('#markup' => $settings['test_formatter_setting_multiple'] . '|' . implode('|', $array));
break;
}
diff --git a/modules/field/tests/field_test.module b/modules/field/tests/field_test.module
index 7f43fbf09..0015cd905 100644
--- a/modules/field/tests/field_test.module
+++ b/modules/field/tests/field_test.module
@@ -183,30 +183,6 @@ function field_test_field_create_field($field) {
}
/**
- * Memorize calls to hook_field_insert().
- */
-function field_test_field_insert($entity_type, $entity, $field, $instance, $items) {
- $args = func_get_args();
- field_test_memorize(__FUNCTION__, $args);
-}
-
-/**
- * Memorize calls to hook_field_update().
- */
-function field_test_field_update($entity_type, $entity, $field, $instance, $items) {
- $args = func_get_args();
- field_test_memorize(__FUNCTION__, $args);
-}
-
-/**
- * Memorize calls to hook_field_delete().
- */
-function field_test_field_delete($entity_type, $entity, $field, $instance, $items) {
- $args = func_get_args();
- field_test_memorize(__FUNCTION__, $args);
-}
-
-/**
* Implements hook_entity_query_alter().
*/
function field_test_entity_query_alter(&$query) {
@@ -248,3 +224,38 @@ function field_test_field_attach_view_alter(&$output, $context) {
$output['test_field'][] = array('#markup' => 'field_test_field_attach_view_alter');
}
}
+
+/**
+ * Implements hook_field_widget_properties_alter().
+ */
+function field_test_field_widget_properties_alter(&$widget, $context) {
+ // Make the alter_test_text field 42 characters for nodes and comments.
+ if (in_array($context['entity_type'], array('node', 'comment')) && ($context['field']['field_name'] == 'alter_test_text')) {
+ $widget['settings']['size'] = 42;
+ }
+}
+
+/**
+ * Implements hook_field_widget_properties_ENTITY_TYPE_alter().
+ */
+function field_test_field_widget_properties_user_alter(&$widget, $context) {
+ // Always use buttons for the alter_test_options field on user forms.
+ if ($context['field']['field_name'] == 'alter_test_options') {
+ $widget['type'] = 'options_buttons';
+ }
+}
+
+/**
+ * Implements hook_field_widget_form_alter().
+ */
+function field_test_field_widget_form_alter(&$element, &$form_state, $context) {
+ switch ($context['field']['field_name']) {
+ case 'alter_test_text':
+ drupal_set_message('Field size: ' . $context['instance']['widget']['settings']['size']);
+ break;
+
+ case 'alter_test_options':
+ drupal_set_message('Widget type: ' . $context['instance']['widget']['type']);
+ break;
+ }
+}