drupal/modules/field/field.crud.inc

697 lines
26 KiB
PHP

<?php
// $Id$
/**
* @file
* Field CRUD API, handling field and field instance creation and deletion.
*/
/**
* @defgroup field_structs Field API data structures
* @{
* Represent Field API fields and instances.
*
* The Field API defines two primary data structures, Field and
* Instance, and the concept of a Bundle. A Field defines a
* particular type of data that can be attached to objects. A Field
* Instance is a Field attached to a single Bundle. A Bundle is a set
* of fields that are treated as a group by the Field Attach API.
*
* For example, suppose a site administrator wants Article nodes to
* have a subtitle and photo. Using the Field API or some UI module,
* the administrator creates a field named 'subtitle' of type 'text'
* and a field named 'photo' of type 'image'. The administrator
* (again, via a UI) creates two Field Instances, one attaching the
* field 'subtitle' to the bundle 'article' and one attaching the field
* 'photo' to the bundle 'article'. When the node system uses the
* Field Attach API to load all fields for an Article node, it passes
* the node's content type (which is 'article') as the node's bundle.
* field_attach_load() then loads the 'subtitle' and 'photo' fields
* because they are both attached to the bundle 'article'.
*
* Field objects are (currently) represented as an array of key/value
* pairs. The object properties are:
*
* @param array $field:
* - id (integer, read-only)
* The primary identifier of the field. It is assigned automatically
* by field_create_field().
* - field_name (string)
* The name of the field. Each field name is unique within Field API.
* When a field is attached to an object, the field's data is stored
* in $object->$field_name.
* - type (string)
* The type of the field, such as 'text' or 'image'. Field types
* are defined by modules that implement hook_field_info().
* - cardinality (integer)
* The number of values the field can hold. Legal values are any
* positive integer or FIELD_CARDINALITY_UNLIMITED.
* - locked (integer)
* TODO: undefined.
* - module (string, read-only)
* The name of the module that implements the field type.
* - active (integer, read-only)
* TRUE if the module that implements the field type is currently
* enabled, FALSE otherwise.
* - deleted (integer, read-only)
* TRUE if this field has been deleted, FALSE otherwise. Deleted
* fields are ignored by the Field Attach API. This property exists
* because fields can be marked for deletion but only actually
* destroyed by a separate garbage-collection process.
* - columns (array, read-only).
* An array of the Field API columns used to store each value of
* this field. The column list may depend on field settings; it is
* not constant per field type. Field API column specifications are
* exactly like Schema API column specifications but, depending on
* the field storage module in use, the name of the column may not
* represent an actual column in an SQL database.
* - indexes (array).
* An array of indexes on data columns, using the same definition format
* as Schema API index specifications. Only columns that appear in the
* 'columns' setting are allowed. Note that field types can specify
* default indexes, which can be modified or added to when
* creating a field.
* - settings (array)
* A sub-array of key/value pairs of field-type-specific settings. Each
* field type module defines and documents its own field settings.
*
* Field Instance objects are (currently) represented as an array of
* key/value pairs. The object properties are:
*
* @param array $instance:
* - id (integer, read-only)
* The primary identifier of this field instance. It is assigned
* automatically by field_create_instance().
* - field_id (integer, read-only)
* The foreign key of the field attached to the bundle by this instance.
* It is populated automatically by field_create_instance().
* - field_name (string)
* The name of the field attached to the bundle by this instance.
* - bundle (string)
* The name of the bundle that the field is attached to.
* - label (string)
* A human-readable label for the field when used with this
* bundle. For example, the label will be the title of Form API
* elements for this instance.
* - description (string)
* A human-readable description for the field when used with this
* bundle. For example, the description will be the help text of
* Form API elements for this instance.
* - weight (float)
* The order in which the field should be sorted relative
* to other fields when used with this bundle. The weight affects
* ordering in both forms (see field_attach_form()) and rendered output
* (see field_attach_view()).
* - required (integer)
* TRUE if a value for this field is required when used with this
* bundle, FALSE otherwise. Currently, required-ness is only enforced
* during Form API operations, not by field_attach_load(),
* field_attach_insert(), or field_attach_update().
* - default_value_function (string)
* The name of the function, if any, that will provide a default value.
* - deleted (integer, read-only)
* TRUE if this instance has been deleted, FALSE otherwise.
* Deleted instances are ignored by the Field Attach API.
* This property exists because instances can be marked for deletion but
* only actually destroyed by a separate garbage-collection process.
* - settings (array)
* A sub-array of key/value pairs of field-type-specific instance
* settings. Each field type module defines and documents its own
* instance settings.
* - widget (array)
* A sub-array of key/value pairs identifying the Form API input widget
* for the field when used by this bundle.
* - type (string)
* The type of the widget, such as text_textfield. Widget types
* are defined by modules that implement hook_field_widget_info().
* - module (string, read-only)
* The name of the module that implements the widget type.
* - active (integer, read-only)
* TRUE if the module that implements the widget type is currently
* enabled, FALSE otherwise.
* - settings (array)
* A sub-array of key/value pairs of widget-type-specific settings.
* Each field widget type module defines and documents its own
* widget settings.
* - display (array)
* A sub-array of key/value pairs identifying build modes and the way the
* field values should be displayed in each build mode.
* - full (array)
* A sub-array of key/value pairs of the display options to be used
* when the field is being displayed in the "full" build mode.
* - label (string)
* Position of the label. 'inline', 'above' and 'hidden' are the
* values recognized by the default 'field' theme implementation.
* - type (string)
* The type of the display formatter, or 'hidden' for no display.
* - settings (array)
* A sub-array of key/value pairs of display options specific to
* the formatter.
* - module (string, read-only)
* The name of the module which implements the display formatter.
* - teaser
* - ...
* - other_build_mode
* - ...
*
* TODO D7 : document max length for field types, widget types,
* formatter names...
*/
/**
* @} End of "defgroup field_structs".
*/
/**
* @defgroup field_crud Field CRUD API
* @{
* Create, update, and delete Field API fields, bundles, and instances.
*
* Modules use this API, often in hook_install(), to create custom
* data structures. UI modules will use it to create a user interface.
*
* The Field CRUD API uses
* @link field_structs Field API data structures @endlink.
*/
/**
* Create a field.
*
* This function does not bind the field to any bundle; use
* field_create_instance() for that.
*
* @param $field
* A field structure. The field_name and type properties are required.
* Other properties, if omitted, will be given the following default values:
* - cardinality: 1
* - locked: FALSE
* - indexes: the field-type indexes, specified by the field type's
* hook_field_schema(). The indexes specified in $field are added
* to those default indexes. It is possible to override the
* definition of a field-type index by providing an index with the
* same name, or to remove it by redefining it as an empty array
* of columns. Overriding field-type indexes should be done
* carefully, for it might seriously affect the site's performance.
* - settings: each omitted setting is given the default value defined in
* hook_field_info().
* @return
* The $field structure with the id property filled in.
* @throw
* FieldException
*/
function field_create_field($field) {
// Field name is required.
if (empty($field['field_name'])) {
throw new FieldException('Attempt to create an unnamed field.');
}
// Field type is required.
if (empty($field['type'])) {
throw new FieldException('Attempt to create a field with no type.');
}
// Field name cannot contain invalid characters.
if (preg_match('/[^a-z0-9_]/', $field['field_name'])) {
throw new FieldException('Attempt to create a field with invalid characters. Only alphanumeric characters and underscores are allowed.');
}
// TODO: check that field_name < 32 chars.
// Check that the field type is known.
$field_type = field_info_field_types($field['type']);
if (!$field_type) {
throw new FieldException(t('Attempt to create a field of unknown type %type.', array('%type' => $field['type'])));
}
// Ensure the field name is unique over active and disabled fields.
// We do not care about deleted fields.
// TODO : do we want specific messages when clashing with a disabled or inactive field ?
$prior_field = field_read_field($field['field_name'], array('include_inactive' => TRUE));
if (!empty($prior_field)) {
throw new FieldException(t('Attempt to create field name %name which already exists.', array('%name' => $field['field_name'])));
}
$field += array(
'cardinality' => 1,
'locked' => FALSE,
'settings' => array(),
);
// Create all per-field-type properties (needed here as long as we have
// settings that impact column definitions).
$field['settings'] += field_info_field_settings($field['type']);
$field['module'] = $field_type['module'];
$field['active'] = 1;
$field['deleted'] = 0;
// Collect storage information.
$schema = (array) module_invoke($field['module'], 'field_schema', $field);
$schema += array('columns' => array(), 'indexes' => array());
// 'columns' are hardcoded in the field type.
$field['columns'] = $schema['columns'];
// 'indexes' can be both hardcoded in the field type, and specified in the
// incoming $field definition.
$field += array(
'indexes' => array(),
);
$field['indexes'] += $schema['indexes'];
// The serialized 'data' column contains everything from $field that does not
// have its own column and is not automatically populated when the field is
// read.
$data = $field;
unset($data['columns'], $data['field_name'], $data['type'], $data['locked'], $data['module'], $data['cardinality'], $data['active'], $data['deleted']);
$field['data'] = $data;
// Store the field and create the id.
drupal_write_record('field_config', $field);
// Invoke hook_field_storage_create_field after the field is
// complete (e.g. it has its id).
module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'field_storage_create_field', $field);
// Clear caches
field_cache_clear(TRUE);
// Invoke external hooks after the cache is cleared for API consistency.
module_invoke_all('field_create_field', $field);
return $field;
}
/**
* Read a single field record directly from the database. Generally,
* you should use the field_info_field() instead.
*
* This function will not return deleted fields. Use
* field_read_fields() instead for this purpose.
*
* @param $field_name
* The field name to read.
* @param array $include_additional
* The default behavior of this function is to not return a field that
* is inactive. Setting
* $include_additional['include_inactive'] to TRUE will override this
* behavior.
* @return
* A field structure, or FALSE.
*/
function field_read_field($field_name, $include_additional = array()) {
$fields = field_read_fields(array('field_name' => $field_name), $include_additional);
return $fields ? current($fields) : FALSE;
}
/**
* Read in fields that match an array of conditions.
*
* @param array $params
* An array of conditions to match against.
* @param array $include_additional
* The default behavior of this function is to not return fields that
* are inactive or have been deleted. Setting
* $include_additional['include_inactive'] or
* $include_additional['include_deleted'] to TRUE will override this
* behavior.
* @return
* An array of fields matching $params. If
* $include_additional['include_deletd'] is TRUE, the array is keyed
* by field id, otherwise it is keyed by field name.
*/
function field_read_fields($params = array(), $include_additional = array()) {
$query = db_select('field_config', 'fc', array('fetch' => PDO::FETCH_ASSOC));
$query->fields('fc');
// Turn the conditions into a query.
foreach ($params as $key => $value) {
$query->condition($key, $value);
}
if (!isset($include_additional['include_inactive']) || !$include_additional['include_inactive']) {
$query->condition('fc.active', 1);
}
$include_deleted = (isset($include_additional['include_deleted']) && $include_additional['include_deleted']);
if (!$include_deleted) {
$query->condition('fc.deleted', 0);
}
$fields = array();
$results = $query->execute();
foreach ($results as $field) {
// Extract serialized data.
$data = unserialize($field['data']);
unset($field['data']);
$field += $data;
// Make sure all settings expected in the current execution context are
// present.
$field['settings'] += field_info_field_settings($field['type']);
module_invoke_all('field_read_field', $field);
// Populate storage information.
$schema = (array) module_invoke($field['module'], 'field_schema', $field);
$schema += array('columns' => array(), 'indexes' => array());
$field['columns'] = $schema['columns'];
$field_name = $field['field_name'];
if ($include_deleted) {
$field_name = $field['id'];
}
$fields[$field_name] = $field;
}
return $fields;
}
/**
* Mark a field for deletion, including all its instances and all data
* associated with it.
*
* @param $field_name
* The field name to delete.
*/
function field_delete_field($field_name) {
// Mark field storage for deletion.
module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'field_storage_delete_field', $field_name);
// Mark any instances of the field for deletion.
db_update('field_config_instance')
->fields(array('deleted' => 1))
->condition('field_name', $field_name)
->execute();
// Mark the field for deletion.
db_update('field_config')
->fields(array('deleted' => 1))
->condition('field_name', $field_name)
->execute();
// Clear the cache.
field_cache_clear(TRUE);
}
/**
* Creates an instance of a field, binding it to a bundle.
*
* @param $instance
* A field instance structure. The field_name and bundle properties are
* required.
* Other properties, if omitted, will be given the following default values:
* - label: the field name
* - description: empty string
* - weight: 0
* - required: FALSE
* - default_value_function: empty string
* - settings: each omitted setting is given the default value specified in
* hook_field_info().
* - widget:
* - type: the default widget specified in hook_field_info().
* - settings: each omitted setting is given the default value specified in
* hook_field_widget_info().
* - display:
* Settings for the 'full' build mode will be added, and each build mode
* will be completed with the follwong default values:
* - label: 'above'
* - type: the default formatter specified in hook_field_info().
* - settings: each omitted setting is given the default value specified in
* hook_field_formatter_info().
* @return
* The $instance structure with the id property filled in.
* @throw
* FieldException
*/
function field_create_instance($instance) {
// Check that the specified field exists.
$field = field_read_field($instance['field_name']);
if (empty($field)) {
throw new FieldException("Attempt to create an instance of a field that doesn't exist.");
}
// Set the field id.
$instance['field_id'] = $field['id'];
// Note that we do *not* prevent creating a field on non-existing bundles,
// because that would break the 'Body as field' upgrade for contrib
// node types.
// TODO: Check that the widget type is known and can handle the field type ?
// TODO: Check that the formatters are known and can handle the field type ?
// TODO: Check that the display build modes are known for the object type ?
// Those checks should probably happen in _field_write_instance() ?
// Problem : this would mean that a UI module cannot update an instance with a disabled formatter.
// Ensure the field instance is unique.
// TODO : do we want specific messages when clashing with a disabled or inactive instance ?
$prior_instance = field_read_instance($instance['field_name'], $instance['bundle'], array('include_inactive' => TRUE));
if (!empty($prior_instance)) {
throw new FieldException('Attempt to create a field instance which already exists.');
}
_field_write_instance($instance);
// Clear caches
field_cache_clear();
// Invoke external hooks after the cache is cleared for API consistency.
module_invoke_all('field_create_instance', $instance);
return $instance;
}
/*
* Update an instance of a field.
*
* @param $instance
* An associative array represeting an instance structure. The required
* keys and values are:
* field_name: The name of an existing field.
* bundle: The bundle this field belongs to.
* Read-only_id properties are assigned automatically. Any other
* properties specified in $instance overwrite the existing values for
* the instance.
* @throw
* FieldException
* @see field_create_instance()
*/
function field_update_instance($instance) {
// Check that the specified field exists.
$field = field_read_field($instance['field_name']);
if (empty($field)) {
throw new FieldException("Attempt to update an instance of a nonexistent field.");
}
// Check that the field instance exists (even if it is inactive, since we
// want to be able to replace inactive widgets with new ones).
$prior_instance = field_read_instance($instance['field_name'], $instance['bundle'], array('include_inactive' => TRUE));
if (empty($prior_instance)) {
throw new FieldException("Attempt to update a field instance that doesn't exist.");
}
$instance['id'] = $prior_instance['id'];
$instance['field_id'] = $prior_instance['field_id'];
_field_write_instance($instance, TRUE);
// Clear caches.
field_cache_clear();
}
/**
* Store an instance record in the field configuration database.
*
* @param $instance
* An instance structure.
* @param $update
* Whether this is a new or existing instance.
*/
function _field_write_instance($instance, $update = FALSE) {
$field = field_read_field($instance['field_name']);
$field_type = field_info_field_types($field['type']);
// Set defaults.
$instance += array(
'settings' => array(),
'display' => array(),
'widget' => array(),
'required' => FALSE,
'label' => $instance['field_name'],
'description' => '',
'weight' => 0,
'deleted' => 0,
);
// Set default instance settings.
$instance['settings'] += field_info_instance_settings($field['type']);
// Set default widget and settings.
$instance['widget'] += array(
// TODO: what if no 'default_widget' specified ?
'type' => $field_type['default_widget'],
'settings' => array(),
);
// Check widget module.
$widget_type = field_info_widget_types($instance['widget']['type']);
$widget_module = $widget_type['module'];
$widget_active = module_exists($widget_module);
$instance['widget']['settings'] += field_info_widget_settings($instance['widget']['type']);
$instance['widget']['module'] = $widget_module;
$instance['widget']['active'] = $widget_active;
// Make sure there is at least display info for the 'full' build mode.
$instance['display'] += array(
'full' => array(),
);
// Set default display settings for each build mode.
foreach ($instance['display'] as $build_mode => $display) {
$instance['display'][$build_mode] += array(
'label' => 'above',
// TODO: what if no 'default_formatter' specified ?
'type' => $field_type['default_formatter'],
'settings' => array(),
);
$formatter_type = field_info_formatter_types($instance['display'][$build_mode]['type']);
// TODO : 'hidden' will raise PHP warnings.
$instance['display'][$build_mode]['module'] = $formatter_type['module'];
$instance['display'][$build_mode]['settings'] += field_info_formatter_settings($instance['display'][$build_mode]['type']);
}
// The serialized 'data' column contains everything from $instance that does
// not have its own column and is not automatically populated when the
// instance is read.
$data = $instance;
unset($data['id'], $data['field_id'], $data['field_name'], $data['bundle'], $data['widget']['type'], $data['weight'], $data['deleted']);
$record = array(
'field_id' => $instance['field_id'],
'field_name' => $instance['field_name'],
'bundle' => $instance['bundle'],
'widget_type' => $instance['widget']['type'],
'widget_module' => $widget_module,
'widget_active' => $widget_active,
'weight' => $instance['weight'],
'data' => $data,
'deleted' => $instance['deleted'],
);
// We need to tell drupal_update_record() the primary keys to trigger an
// update.
if ($update) {
$record['id'] = $instance['id'];
$primary_key = array('id');
}
else {
$primary_key = array();
}
drupal_write_record('field_config_instance', $record, $primary_key);
}
/**
* Read a single instance record directly from the database. Generally,
* you should use the field_info_instance() instead.
*
* This function will not return deleted instances. Use
* field_read_instances() instead for this purpose.
*
* @param $field_name
* The field name to read.
* @param $bundle
* The bundle to which the field is bound.
* @param array $include_additional
* The default behavior of this function is to not return an instance that
* is inactive. Setting
* $include_additional['include_inactive'] to TRUE will override this
* behavior.
* @return
* An instance structure, or FALSE.
*/
function field_read_instance($field_name, $bundle, $include_additional = array()) {
$instances = field_read_instances(array('field_name' => $field_name, 'bundle' => $bundle), $include_additional);
return $instances ? current($instances) : FALSE;
}
/**
* Read in field instances that match an array of conditions.
*
* @param $param
* An array of properties to use in selecting a field
* instance. Valid keys include any column of the
* field_config_instance table. If NULL, all instances will be returned.
* @param $include_additional
* The default behavior of this function is to not return field
* instances that are inactive or have been marked deleted. Setting
* $include_additional['include_inactive'] or
* $include_additional['include_deleted'] to TRUE will override this
* behavior.
* @return
* An array of instances matching the arguments.
*/
function field_read_instances($params = array(), $include_additional = array()) {
$query = db_select('field_config_instance', 'fci', array('fetch' => PDO::FETCH_ASSOC));
$query->join('field_config', 'fc', 'fc.id = fci.field_id');
$query->fields('fci');
$query->fields('fc', array('type'));
// Turn the conditions into a query.
foreach ($params as $key => $value) {
$query->condition('fci.' . $key, $value);
}
$query->condition('fc.active', 1);
if (!isset($include_additional['include_inactive']) || !$include_additional['include_inactive']) {
$query->condition('fci.widget_active', 1);
}
if (!isset($include_additional['include_deleted']) || !$include_additional['include_deleted']) {
$query->condition('fc.deleted', 0);
$query->condition('fci.deleted', 0);
}
$instances = array();
$results = $query->execute();
foreach ($results as $record) {
$instance = unserialize($record['data']);
$instance['id'] = $record['id'];
$instance['field_id'] = $record['field_id'];
$instance['field_name'] = $record['field_name'];
$instance['bundle'] = $record['bundle'];
$instance['weight'] = $record['weight'];
$instance['deleted'] = $record['deleted'];
$instance['widget']['type'] = $record['widget_type'];
$instance['widget']['module'] = $record['widget_module'];
$instance['widget']['active'] = $record['widget_active'];
// Make sure all settings expected in the current execution context are
// present.
$instance['settings'] += field_info_instance_settings($record['type']);
$instance['widget']['settings'] += field_info_widget_settings($instance['widget']['type']);
foreach ($instance['display'] as $build_mode => $display) {
$instance['display'][$build_mode]['settings'] += field_info_formatter_settings($display['type']);
}
module_invoke_all('field_read_instance', $instance);
$instances[] = $instance;
}
return $instances;
}
/**
* Mark a field instance for deletion, including all data associated with
* it.
*
* @param $field_name
* The name of the field whose instance will be deleted.
* @param $bundle
* The bundle for the instance which will be deleted.
*/
function field_delete_instance($field_name, $bundle) {
// Mark the field instance for deletion.
db_update('field_config_instance')
->fields(array('deleted' => 1))
->condition('field_name', $field_name)
->condition('bundle', $bundle)
->execute();
// Mark all data associated with the field for deletion.
module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'field_storage_delete_instance', $field_name, $bundle);
// Clear the cache.
field_cache_clear();
}
/**
* @} End of "defgroup field_crud".
*/