Commit f6ccf978 authored by webchick's avatar webchick

#488542 by yched: Allow field UI to be attached to any fieldable entity.

parent e916edc7
......@@ -9,47 +9,81 @@
/**
* Inform the Field API about one or more fieldable types.
*
* Inform the Field API about one or more fieldable types, (object
* types to which fields can be attached).
* Inform the Field API about one or more fieldable types (object types to
* which fields can be attached).
*
* @return
* An array whose keys are fieldable object type names and
* whose values identify properties of those types that the Field
* system needs to know about:
*
* name: The human-readable name of the type.
* id key: The object property that contains the primary id for the
* object. Every object passed to the Field API must
* have this property and its value must be numeric.
* revision key: The object property that contains the revision id
* for the object, or NULL if the object type is not
* versioned. The Field API assumes that all revision ids are
* unique across all instances of a type; this means, for example,
* that every object's revision ids cannot be 0, 1, 2, ...
* bundle key: The object property that contains the bundle name for
* the object (bundle name is what nodes call "content type").
* The bundle name defines which fields are connected to the object.
* cacheable: A boolean indicating whether Field API should cache
* whose values are arrays with the following key/value pairs:
* - label: The human-readable name of the type.
* - object keys: An array describing how the Field API can extract the
* informations it needs from the objects of the type.
* - id: The name of the property that contains the primary id of the
* object. Every object passed to the Field API must have this property
* and its value must be numeric.
* - revision: The name of the property that contains the revision id of
* the object. The Field API assumes that all revision ids are unique
* across all objects of a type.
* This element can be omitted if the objects of this type are not
* versionable.
* - bundle: The name of the property that contains the bundle name for the
* object. The bundle name defines which set of fields are attached to
* the object (e.g. what nodes call "content type").
* This element can be omitted if this type has no bundles (all objects
* have the same fields).
* - bundle keys: An array describing how the Field API can extract the
* informations it needs from the bundle objects for this type (e.g
* $vocabulary objects for terms; not applicable for nodes).
* This element can be omitted if this type's bundles do not exist as
* standalone objects.
* - bundle: The name of the property that contains the name of the bundle
* object.
* - cacheable: A boolean indicating whether Field API should cache
* loaded fields for each object, reducing the cost of
* field_attach_load().
* bundles: An array of all existing bundle names for this object
* type. TODO: Define format. TODO: I'm unclear why we need
* this.
* - bundles: An array describing all bundles for this object type.
* Keys are bundles machine names, as found in the objects' 'bundle'
* property (defined in the 'object keys' entry above).
* - label: The human-readable name of the bundle.
* - admin: An array of informations that allow Field UI pages (currently
* implemented in a contributed module) to attach themselves to the
* existing administration pages for the bundle.
* - path: the path of the bundle's main administration page, as defined
* in hook_menu(). If the path includes a placeholder for the bundle,
* the 'bundle argument', 'bundle helper' and 'real path' keys below
* are required.
* - bundle argument: The position of the placeholder in 'path', if any.
* - real path: The actual path (no placeholder) of the bundle's main
* administration page. This will be used to generate links.
* - access callback: As in hook_menu(). 'user_access' will be assumed if
* no value is provided.
* - access arguments: As in hook_menu().
*/
function hook_fieldable_info() {
$return = array(
'node' => array(
'name' => t('Node'),
'id key' => 'nid',
'revision key' => 'vid',
'bundle key' => 'type',
// Node.module handles its own caching.
'cacheable' => FALSE,
// Bundles must provide human readable name so
// we can create help and error messages about them.
'bundles' => node_type_get_names(),
'taxonomy_term' => array(
'label' => t('Taxonomy term'),
'object keys' => array(
'id' => 'tid',
'bundle' => 'vocabulary_machine_name',
),
'bundle keys' => array(
'bundle' => 'machine_name',
),
'bundles' => array(),
),
);
foreach (taxonomy_get_vocabularies() as $vocabulary) {
$return['taxonomy_term']['bundles'][$vocabulary->machine_name] = array(
'label' => $vocabulary->name,
'admin' => array(
'path' => 'admin/content/taxonomy/%taxonomy_vocabulary',
'real path' => 'admin/content/taxonomy/' . $vocabulary->vid,
'bundle argument' => 3,
'access arguments' => array('administer taxonomy'),
),
);
}
return $return;
}
......
......@@ -382,8 +382,8 @@ function field_attach_form($obj_type, $object, &$form, &$form_state) {
* The type of $object; e.g. 'node' or 'user'.
* @param $objects
* An array of objects for which to load fields, keyed by object id.
* Each object needs to have its 'bundle key', 'id key' and (if applicable)
* 'revision key' filled.
* Each object needs to have its 'bundle', 'id' and (if applicable)
* 'revision' keys filled.
* @param $age
* FIELD_LOAD_CURRENT to load the most recent revision for all
* fields, or FIELD_LOAD_REVISION to load the version indicated by
......@@ -485,8 +485,8 @@ function field_attach_load($obj_type, $objects, $age = FIELD_LOAD_CURRENT) {
* The type of $object; e.g. 'node' or 'user'.
* @param $objects
* An array of objects for which to load fields, keyed by object id.
* Each object needs to have its 'bundle key', 'id key' and 'revision key'
* filled.
* Each object needs to have its 'bundle', 'id' and (if applicable)
* 'revision' keys filled.
* @returns
* On return, the objects in $objects are modified by having the
* appropriate set of fields added.
......@@ -546,8 +546,8 @@ function field_attach_validate($obj_type, $object) {
* @param $obj_type
* The type of $object; e.g. 'node' or 'user'.
* @param $object
* The object being submitted. The 'bundle key', 'id key' and (if applicable)
* 'revision key' should be present. The actual field values will be read
* The object being submitted. The 'bundle', 'id' and (if applicable)
* 'revision' keys should be present. The actual field values will be read
* from $form_state['values'].
* @param $form
* The form structure.
......@@ -578,8 +578,8 @@ function field_attach_form_validate($obj_type, $object, $form, &$form_state) {
* @param $obj_type
* The type of $object; e.g. 'node' or 'user'.
* @param $object
* The object being submitted. The 'bundle key', 'id key' and (if applicable)
* 'revision key' should be present. The actual field values will be read
* The object being submitted. The 'bundle', 'id' and (if applicable)
* 'revision' keys should be present. The actual field values will be read
* from $form_state['values'].
* @param $form
* The form structure to fill in.
......@@ -993,19 +993,41 @@ function field_attach_delete_bundle($bundle) {
* 2: bundle name of the object
* 3: whether $obj_type's fields should be cached (TRUE/FALSE)
*/
function field_attach_extract_ids($object_type, $object) {
function field_attach_extract_ids($obj_type, $object) {
// TODO D7 : prevent against broken 3rd party $node without 'type'.
$info = field_info_fieldable_types($object_type);
$info = field_info_fieldable_types($obj_type);
// Objects being created might not have id/vid yet.
$id = isset($object->{$info['id key']}) ? $object->{$info['id key']} : NULL;
$vid = ($info['revision key'] && isset($object->{$info['revision key']})) ? $object->{$info['revision key']} : NULL;
$id = isset($object->{$info['object keys']['id']}) ? $object->{$info['object keys']['id']} : NULL;
$vid = ($info['object keys']['revision'] && isset($object->{$info['object keys']['revision']})) ? $object->{$info['object keys']['revision']} : NULL;
// If no bundle key provided, then we assume a single bundle, named after the
// type of the object.
$bundle = $info['bundle key'] ? $object->{$info['bundle key']} : $object_type;
$bundle = $info['object keys']['bundle'] ? $object->{$info['object keys']['bundle']} : $obj_type;
$cacheable = $info['cacheable'];
return array($id, $vid, $bundle, $cacheable);
}
/**
* Helper function to extract id, vid, and bundle name from an object.
*
* @param $obj_type
* The type of $object; e.g. 'node' or 'user'.
* @param $bundle
* The bundle object (or string if bundles for this object type do not exist
* as standalone objects).
* @return
* The bundle name.
*/
function field_attach_extract_bundle($obj_type, $bundle) {
if (is_string($bundle)) {
return $bundle;
}
$info = field_info_fieldable_types($obj_type);
if (is_object($bundle) && isset($info['bundle keys']['bundle']) && isset($bundle->{$info['bundle keys']['bundle']})) {
return $bundle->{$info['bundle keys']['bundle']};
}
}
/**
* Helper function to assemble an object structure with initial ids.
*
......@@ -1025,12 +1047,12 @@ function field_attach_extract_ids($object_type, $object) {
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];
$object->{$info['object keys']['id']} = $ids[0];
if (isset($info['object keys']['revision']) && !is_null($ids[1])) {
$object->{$info['object keys']['revision']} = $ids[1];
}
if ($info['bundle key']) {
$object->{$info['bundle key']} = $ids[2];
if ($info['object keys']['bundle']) {
$object->{$info['object keys']['bundle']} = $ids[2];
}
return $object;
}
......
......@@ -126,16 +126,18 @@ function _field_info_collate_types($reset = FALSE) {
foreach ($fieldable_types as $name => $fieldable_info) {
// Provide defaults.
$fieldable_info += array(
'revision key' => '',
'bundle key' => '',
'cacheable' => TRUE,
'bundles' => array(),
);
$fieldable_info['object keys'] += array(
'revision' => '',
'bundle' => '',
);
// If no bundle key provided, then we assume a single bundle, named
// after the type of the object. Make sure the bundle created
// has the human-readable name we need for bundle messages.
if (empty($fieldable_info['bundle key'])) {
$fieldable_info['bundles'] = array($name => $fieldable_info['name']);
if (empty($fieldable_info['object keys']['bundle']) && empty($fieldable_info['bundles'])) {
$fieldable_info['bundles'] = array($name => array('label' => $fieldable_info['label']));
}
$info['fieldable types'][$name] = $fieldable_info;
$info['fieldable types'][$name]['module'] = $module;
......@@ -367,7 +369,7 @@ function field_info_bundles($obj_type = NULL) {
}
/**
* Identity the type of entity that created a bundle.
* Identify the type of entity that created a bundle.
* // TODO : might not be needed depending on how we solve
* // the 'namespace bundle names' issue
*/
......
......@@ -412,23 +412,23 @@ function field_sql_storage_field_storage_query($field_name, $conditions, $count,
$query->range($cursor, $limit);
}
$results = $query->execute();
$found = FALSE;
foreach ($results as $row) {
$found = TRUE;
++$cursor;
// 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;
$id = ($load_current || empty($entity_type['object keys']['revision'])) ? $row->entity_id : $row->revision_id;
// We get multiple rows if the field has multiple deltas. Only
// count the first one.
if (isset($return[$row->type][$id])) {
continue;
}
$return[$row->type][$id] = field_attach_create_stub_object($row->type, array($row->entity_id, $row->revision_id, $row->bundle));
--$count;
}
......
......@@ -124,17 +124,28 @@ function node_cron() {
function node_fieldable_info() {
$return = array(
'node' => array(
'name' => t('Node'),
'id key' => 'nid',
'revision key' => 'vid',
'bundle key' => 'type',
'label' => t('Node'),
'object keys' => array(
'id' => 'nid',
'revision' => 'vid',
'bundle' => 'type',
),
// Node.module handles its own caching.
// 'cacheable' => FALSE,
// Bundles must provide human readable name so
// we can create help and error messages about them.
'bundles' => node_type_get_names(),
'bundles' => array(),
),
);
// Bundles must provide a human readable name so we can create help and error
// messages, and the path to attach Field admin pages to.
foreach (node_type_get_names() as $type => $name) {
$return['node']['bundles'][$type] = array(
'label' => $name,
'admin' => array(
'path' => 'admin/build/node-type/' . str_replace('_', '-', $type),
'access arguments' => array('administer content types'),
),
);
}
return $return;
}
......
......@@ -26,12 +26,12 @@ function field_test_permission() {
*/
function field_test_menu() {
$items = array();
$info = field_test_fieldable_info();
$bundles = field_info_bundles('test_entity');
foreach (array_keys($info['test_entity']['bundles']) as $bundle) {
$bundle_url_str = str_replace('_', '-', $bundle);
foreach ($bundles as $bundle_name => $bundle_info) {
$bundle_url_str = str_replace('_', '-', $bundle_name);
$items['test-entity/add/' . $bundle_url_str] = array(
'title' => "Add $bundle test_entity",
'title' => t('Add %bundle test_entity', array('%bundle' => $bundle_info['label'])),
'page callback' => 'field_test_entity_add',
'page arguments' => array(2),
'access arguments' => array('administer field_test content'),
......@@ -61,23 +61,27 @@ function field_test_menu() {
* Define a test fieldable entity.
*/
function field_test_fieldable_info() {
$bundles = variable_get('field_test_bundles', array('test_bundle' => 'Test Bundle'));
$bundles = variable_get('field_test_bundles', array('test_bundle' => array('label' => 'Test Bundle')));
return array(
'test_entity' => array(
'name' => t('Test Entity'),
'id key' => 'ftid',
'revision key' => 'ftvid',
'object keys' => array(
'id' => 'ftid',
'revision' => 'ftvid',
'bundle' => 'fttype',
),
'cacheable' => FALSE,
'bundle key' => 'fttype',
'bundles' => $bundles,
),
// This entity type doesn't get form handling for now...
'test_cacheable_entity' => array(
'name' => t('Test Entity, cacheable'),
'id key' => 'ftid',
'revision key' => 'ftvid',
'object keys' => array(
'id' => 'ftid',
'revision' => 'ftvid',
'bundle' => 'fttype',
),
'cacheable' => TRUE,
'bundle key' => 'fttype',
'bundles' => $bundles,
),
);
......@@ -86,18 +90,18 @@ function field_test_fieldable_info() {
/**
* Create a new bundle for test_entity objects.
*
* @param $bundle
* @param $bundle_name
* The machine-readable name of the bundle.
* @param $text
* The human-readable name of the bundle. If none is provided, the machine
* name will be used.
*/
function field_test_create_bundle($bundle, $text = NULL) {
$bundles = variable_get('field_test_bundles', array('test_bundle' => 'Test Bundle'));
$bundles += array($bundle => $text ? $text : $bundle);
function field_test_create_bundle($bundle_name, $text = NULL) {
$bundles = variable_get('field_test_bundles', array('test_bundle' => array('label' => 'Test Bundle')));
$bundles += array($bundle_name => array('label' => $text ? $text : $bundle_name));
variable_set('field_test_bundles', $bundles);
field_attach_create_bundle($bundle);
field_attach_create_bundle($bundle_name);
}
/**
......@@ -109,7 +113,7 @@ function field_test_create_bundle($bundle, $text = NULL) {
* The new machine-readable name of the bundle.
*/
function field_test_rename_bundle($bundle_old, $bundle_new) {
$bundles = variable_get('field_test_bundles', array('test_bundle' => 'Test Bundle'));
$bundles = variable_get('field_test_bundles', array('test_bundle' => array('label' => 'Test Bundle')));
$bundles[$bundle_new] = $bundles[$bundle_old];
unset($bundles[$bundle_old]);
variable_set('field_test_bundles', $bundles);
......@@ -120,15 +124,15 @@ function field_test_rename_bundle($bundle_old, $bundle_new) {
/**
* Delete a bundle for test_entity objects.
*
* @param $bundle
* @param $bundle_name
* The machine-readable name of the bundle to delete.
*/
function field_test_delete_bundle($bundle) {
$bundles = variable_get('field_test_bundles', array('test_bundle' => 'Test Bundle'));
unset($bundles[$bundle]);
function field_test_delete_bundle($bundle_name) {
$bundles = variable_get('field_test_bundles', array('test_bundle' => array('label' => 'Test Bundle')));
unset($bundles[$bundle_name]);
variable_set('field_test_bundles', $bundles);
field_attach_delete_bundle($bundle);
field_attach_delete_bundle($bundle_name);
}
/**
......
......@@ -24,12 +24,28 @@ function taxonomy_permission() {
function taxonomy_fieldable_info() {
$return = array(
'taxonomy_term' => array(
'name' => t('Taxonomy term'),
'id key' => 'tid',
'bundle key' => 'vocabulary_machine_name',
'bundles' => taxonomy_vocabulary_get_names(),
'label' => t('Taxonomy term'),
'object keys' => array(
'id' => 'tid',
'bundle' => 'vocabulary_machine_name',
),
'bundle keys' => array(
'bundle' => 'machine_name',
),
'bundles' => array(),
),
);
foreach (taxonomy_get_vocabularies() as $vocabulary) {
$return['taxonomy_term']['bundles'][$vocabulary->machine_name] = array(
'label' => $vocabulary->name,
'admin' => array(
'path' => 'admin/content/taxonomy/%taxonomy_vocabulary',
'real path' => 'admin/content/taxonomy/' . $vocabulary->vid,
'bundle argument' => 3,
'access arguments' => array('administer taxonomy'),
),
);
}
return $return;
}
......
......@@ -89,8 +89,19 @@ function user_theme() {
function user_fieldable_info() {
$return = array(
'user' => array(
'name' => t('User'),
'id key' => 'uid',
'label' => t('User'),
'object keys' => array(
'id' => 'uid',
),
'bundles' => array(
'user' => array(
'label' => t('User'),
'admin' => array(
'path' => 'admin/settings/user',
'access arguments' => array('administer users'),
),
),
),
),
);
return $return;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment