Commit fee6ae0e authored by alexpott's avatar alexpott

Issue #2225629 by Xano: Move hook_field_extra_fields() to the entity API.

parent e0f3aa39
......@@ -42,6 +42,13 @@
*/
class EntityManager extends PluginManagerBase implements EntityManagerInterface {
/**
* Extra fields by bundle.
*
* @var array
*/
protected $extraFields = array();
/**
* The injection container that should be passed into the controller factory.
*
......@@ -163,6 +170,7 @@ public function clearCachedDefinitions() {
$this->bundleInfo = NULL;
$this->displayModeInfo = array();
$this->extraFields = array();
}
/**
......@@ -517,6 +525,42 @@ public function getAllBundleInfo() {
return $this->bundleInfo;
}
/**
* {@inheritdoc}
*/
public function getExtraFields($entity_type_id, $bundle) {
// Read from the "static" cache.
if (isset($this->extraFields[$entity_type_id][$bundle])) {
return $this->extraFields[$entity_type_id][$bundle];
}
// Read from the persistent cache. Since hook_entity_extra_field_info() and
// hook_entity_extra_field_info_alter() might contain t() calls, we cache
// per language.
$cache_id = 'entity_bundle_extra_fields:' . $entity_type_id . ':' . $bundle . ':' . $this->languageManager->getCurrentLanguage()->id;
$cached = $this->cache->get($cache_id);
if ($cached) {
$this->extraFields[$entity_type_id][$bundle] = $cached->data;
return $this->extraFields[$entity_type_id][$bundle];
}
$extra = $this->moduleHandler->invokeAll('entity_extra_field_info');
$this->moduleHandler->alter('entity_extra_field_info', $extra);
$info = isset($extra[$entity_type_id][$bundle]) ? $extra[$entity_type_id][$bundle] : array();
$info += array(
'form' => array(),
'display' => array(),
);
// Store in the 'static' and persistent caches.
$this->extraFields[$entity_type_id][$bundle] = $info;
$this->cache->set($cache_id, $info, Cache::PERMANENT, array(
'entity_field_info' => TRUE,
));
return $this->extraFields[$entity_type_id][$bundle];
}
/**
* {@inheritdoc}
*/
......
......@@ -186,6 +186,35 @@ public function getController($entity_type, $controller_type);
*/
public function getBundleInfo($entity_type);
/**
* Retrieves the "extra fields" for a bundle.
*
* @param string $entity_type_id
* The entity type ID.
* @param string $bundle
* The bundle name.
*
* @return array
* A nested array of 'pseudo-field' elements. Each list is nested within the
* following keys: entity type, bundle name, context (either 'form' or
* 'display'). The keys are the name of the elements as appearing in the
* renderable array (either the entity form or the displayed entity). The
* value is an associative array:
* - label: The human readable name of the element. Make sure you sanitize
* this appropriately.
* - description: A short description of the element contents.
* - weight: The default weight of the element.
* - visible: (optional) The default visibility of the element. Defaults to
* TRUE.
* - edit: (optional) String containing markup (normally a link) used as the
* element's 'edit' operation in the administration interface. Only for
* 'form' context.
* - delete: (optional) String containing markup (normally a link) used as the
* element's 'delete' operation in the administration interface. Only for
* 'form' context.
*/
public function getExtraFields($entity_type_id, $bundle);
/**
* Returns the entity translation to be used in the given context.
*
......
......@@ -99,7 +99,7 @@ function hook_comment_load(Drupal\comment\Comment $comments) {
function hook_comment_view(\Drupal\comment\Entity\Comment $comment, \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display, $view_mode, $langcode) {
// Only do the extra work if the component is configured to be displayed.
// This assumes a 'mymodule_addition' extra field has been defined for the
// node type in hook_field_extra_fields().
// node type in hook_entity_extra_field_info().
if ($display->getComponent('mymodule_addition')) {
$comment->content['mymodule_addition'] = array(
'#markup' => mymodule_addition($comment),
......
......@@ -121,9 +121,9 @@ function comment_uri(CommentInterface $comment) {
}
/**
* Implements hook_field_extra_fields().
* Implements hook_entity_extra_field_info().
*/
function comment_field_extra_fields() {
function comment_entity_extra_field_info() {
$return = array();
foreach (\Drupal::service('comment.manager')->getAllFields() as $entity_type => $fields) {
foreach ($fields as $field_name => $field_info) {
......
......@@ -92,9 +92,9 @@ function contact_entity_bundle_info() {
}
/**
* Implements hook_field_extra_fields().
* Implements hook_entity_extra_field_info().
*/
function contact_field_extra_fields() {
function contact_entity_extra_field_info() {
$fields = array();
foreach (array_keys(entity_get_bundles('contact_message')) as $bundle) {
$fields['contact_message'][$bundle]['form']['name'] = array(
......
......@@ -683,9 +683,9 @@ function content_translation_entity_update(EntityInterface $entity) {
}
/**
* Implements hook_field_extra_fields().
* Implements hook_entity_extra_field_info().
*/
function content_translation_field_extra_fields() {
function content_translation_entity_extra_field_info() {
$extra = array();
foreach (\Drupal::entityManager()->getDefinitions() as $entity_type => $info) {
......
......@@ -8,97 +8,6 @@
use Drupal\Component\Utility\NestedArray;
use Drupal\field\FieldConfigUpdateForbiddenException;
/**
* Exposes "pseudo-field" components on fieldable entities.
*
* Field UI's "Manage display" and "Manage form display" pages let users
* re-order fields rendered through the regular widget/formatter pipeline, but
* also other components: entity fields that are rendered through custom code,
* or other arbitrary components added through hook_form_alter() or
* hook_entity_view().
*
* Fieldable entities or modules that want to have their components supported
* should expose them using this hook. The user-defined settings (weight,
* visible) are automatically applied on rendered forms and displayed entities
* by ContentEntityFormController::form() and EntityViewBuilder::viewMultiple().
*
* @see hook_field_extra_fields_alter()
*
* @return array
* A nested array of 'pseudo-field' elements. Each list is nested within the
* following keys: entity type, bundle name, context (either 'form' or
* 'display'). The keys are the name of the elements as appearing in the
* renderable array (either the entity form or the displayed entity). The
* value is an associative array:
* - label: The human readable name of the element. Make sure you sanitize
* this appropriately.
* - description: A short description of the element contents.
* - weight: The default weight of the element.
* - visible: (optional) The default visibility of the element. Defaults to
* TRUE.
* - edit: (optional) String containing markup (normally a link) used as the
* element's 'edit' operation in the administration interface. Only for
* 'form' context.
* - delete: (optional) String containing markup (normally a link) used as the
* element's 'delete' operation in the administration interface. Only for
* 'form' context.
*/
function hook_field_extra_fields() {
$extra = array();
$module_language_enabled = \Drupal::moduleHandler()->moduleExists('language');
$description = t('Node module element');
foreach (node_type_get_types() as $bundle) {
if ($bundle->has_title) {
$extra['node'][$bundle->type]['form']['title'] = array(
'label' => check_plain($bundle->title_label),
'description' => $description,
'weight' => -5,
);
}
// Add also the 'language' select if Language module is enabled and the
// bundle has multilingual support.
// Visibility of the ordering of the language selector is the same as on the
// node/add form.
if ($module_language_enabled) {
$configuration = language_get_default_configuration('node', $bundle->type);
if ($configuration['language_show']) {
$extra['node'][$bundle->type]['form']['language'] = array(
'label' => t('Language'),
'description' => $description,
'weight' => 0,
);
}
}
$extra['node'][$bundle->type]['display']['language'] = array(
'label' => t('Language'),
'description' => $description,
'weight' => 0,
'visible' => FALSE,
);
}
return $extra;
}
/**
* Alter "pseudo-field" components on fieldable entities.
*
* @param $info
* The associative array of 'pseudo-field' components.
*
* @see 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]['form']['title'])) {
$info['node'][$bundle->type]['form']['title']['weight'] = -20;
}
}
}
/**
* @defgroup field_types Field Types API
* @{
......
......@@ -117,9 +117,12 @@ function field_info_fields() {
*
* @return
* The array of pseudo-field elements in the bundle.
*
* @deprecated in Drupal 8.x-dev. Use
* \Drupal::entityManager()->getExtraFields() instead.
*/
function field_info_extra_fields($entity_type, $bundle, $context) {
$info = Field::fieldInfo()->getBundleExtraFields($entity_type, $bundle);
$info = \Drupal::entityManager()->getExtraFields($entity_type, $bundle);
return isset($info[$context]) ? $info[$context] : array();
}
......
......@@ -124,13 +124,6 @@ class FieldInfo {
*/
protected $emptyBundles = array();
/**
* Extra fields by bundle.
*
* @var array
*/
protected $bundleExtraFields = array();
/**
* Constructs this FieldInfo object.
*
......@@ -168,8 +161,6 @@ public function flush() {
$this->loadedAllInstances = FALSE;
$this->emptyBundles = array();
$this->bundleExtraFields = array();
Cache::deleteTags(array('field_info' => TRUE));
}
......@@ -521,47 +512,6 @@ function getInstance($entity_type, $bundle, $field_name) {
}
}
/**
* Retrieves the "extra fields" for a bundle.
*
* @param string $entity_type
* The entity type.
* @param string $bundle
* The bundle name.
*
* @return array
* The array of extra fields.
*/
public function getBundleExtraFields($entity_type, $bundle) {
// Read from the "static" cache.
if (isset($this->bundleExtraFields[$entity_type][$bundle])) {
return $this->bundleExtraFields[$entity_type][$bundle];
}
// Read from the persistent cache. Since hook_field_extra_fields() and
// hook_field_extra_fields_alter() might contain t() calls, we cache per
// language.
$langcode = $this->languageManager->getCurrentLanguage()->id;
if ($cached = $this->cacheBackend->get("field_info:bundle_extra:$langcode:$entity_type:$bundle")) {
$this->bundleExtraFields[$entity_type][$bundle] = $cached->data;
return $this->bundleExtraFields[$entity_type][$bundle];
}
// Cache miss: read from hook_field_extra_fields(). Note: given the current
// shape of the hook, we have no other way than collecting extra fields on
// all bundles.
$extra = $this->moduleHandler->invokeAll('field_extra_fields');
$this->moduleHandler->alter('field_extra_fields', $extra);
$info = isset($extra[$entity_type][$bundle]) ? $extra[$entity_type][$bundle] : array();
$info += array('form' => array(), 'display' => array());
// Store in the 'static' and persistent caches.
$this->bundleExtraFields[$entity_type][$bundle] = $info;
$this->cacheBackend->set("field_info:bundle_extra:$langcode:$entity_type:$bundle", $info, Cache::PERMANENT, array('field_info' => TRUE));
return $this->bundleExtraFields[$entity_type][$bundle];
}
/**
* Prepares a field for the current run-time context.
*
......
......@@ -363,39 +363,4 @@ protected function getExpectedFieldTypeDefinition() {
);
}
/**
* Tests that the extra fields can be translated.
*/
function testFieldInfoExtraFieldsTranslation() {
$this->enableModules(array('language', 'locale'));
$this->installSchema('locale', array('locales_source', 'locales_target', 'locales_location'));
foreach (array('en', 'hu') as $id) {
$language = new Language(array(
'id' => $id,
));
language_save($language);
}
$locale_storage = $this->container->get('locale.storage');
// Create test source string.
$en_string = $locale_storage->createString(array(
'source' => 'User name and password',
'context' => '',
))->save();
// Create translation for new string and save it.
$translated_string = $this->randomString();
$locale_storage->createTranslation(array(
'lid' => $en_string->lid,
'language' => 'hu',
'translation' => $translated_string,
))->save();
// Check that the label is translated.
\Drupal::translation()->setDefaultLangcode('hu');
$field_info = \Drupal::service('field.info');
$user_fields = $field_info->getBundleExtraFields('user', 'user');
$this->assertEqual($user_fields['form']['account']['label'], $translated_string);
}
}
......@@ -194,9 +194,9 @@ function field_test_field_formatter_settings_summary_alter(&$summary, $context)
}
/**
* Implements hook_field_extra_fields_alter().
* Implements hook_entity_extra_field_info_alter().
*/
function field_test_field_extra_fields_alter(&$info) {
function field_test_entity_extra_field_info_alter(&$info) {
// Remove all extra fields from the 'no_fields' content type;
unset($info['node']['no_fields']);
}
......@@ -797,7 +797,7 @@ function hook_node_submit(\Drupal\node\NodeInterface $node, $form, &$form_state)
function hook_node_view(\Drupal\node\NodeInterface $node, \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display, $view_mode, $langcode) {
// Only do the extra work if the component is configured to be displayed.
// This assumes a 'mymodule_addition' extra field has been defined for the
// node type in hook_field_extra_fields().
// node type in hook_entity_extra_field_info().
if ($display->getComponent('mymodule_addition')) {
$node->content['mymodule_addition'] = array(
'#markup' => mymodule_addition($node),
......
......@@ -445,9 +445,9 @@ function node_add_body_field(NodeTypeInterface $type, $label = 'Body') {
}
/**
* Implements hook_field_extra_fields().
* Implements hook_entity_extra_field_info().
*/
function node_field_extra_fields() {
function node_entity_extra_field_info() {
$extra = array();
$module_language_enabled = \Drupal::moduleHandler()->moduleExists('language');
$description = t('Node module element');
......
......@@ -5,6 +5,7 @@
* Hooks provided the Entity module.
*/
use Drupal\Component\Utility\String;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Field\FieldDefinition;
......@@ -456,7 +457,7 @@ function hook_entity_query_alter(\Drupal\Core\Entity\Query\QueryInterface $query
function hook_entity_view(\Drupal\Core\Entity\EntityInterface $entity, \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display, $view_mode, $langcode) {
// Only do the extra work if the component is configured to be displayed.
// This assumes a 'mymodule_addition' extra field has been defined for the
// entity bundle in hook_field_extra_fields().
// entity bundle in hook_entity_extra_field_info().
if ($display->getComponent('mymodule_addition')) {
$entity->content['mymodule_addition'] = array(
'#markup' => mymodule_addition($entity),
......@@ -524,7 +525,7 @@ function hook_entity_prepare_view($entity_type_id, array $entities, array $displ
if (!empty($entities) && $entity_type_id == 'user') {
// Only do the extra work if the component is configured to be
// displayed. This assumes a 'mymodule_addition' extra field has been
// defined for the entity bundle in hook_field_extra_fields().
// defined for the entity bundle in hook_entity_extra_field_info().
$ids = array();
foreach ($entities as $id => $entity) {
if ($displays[$entity->bundle()]->getComponent('mymodule_addition')) {
......@@ -819,6 +820,7 @@ function hook_entity_field_access($operation, \Drupal\Core\Field\FieldDefinition
* (\Drupal\Core\Field\FieldItemListInterface).
*/
function hook_entity_field_access_alter(array &$grants, array $context) {
/** @var \Drupal\Core\Field\FieldDefinitionInterface $field_definition */
$field_definition = $context['field_definition'];
if ($field_definition->getName() == 'field_of_interest' && $grants['node'] === FALSE) {
// Override node module's restriction to no opinion. We don't want to
......@@ -829,3 +831,78 @@ function hook_entity_field_access_alter(array &$grants, array $context) {
$grants['node'] = NULL;
}
}
/**
* Exposes "pseudo-field" components on content entities.
*
* Field UI's "Manage fields" and "Manage display" pages let users re-order
* fields, but also non-field components. For nodes, these include elements
* exposed by modules through hook_form_alter(), for instance.
*
* Content entities or modules that want to have their components supported
* should expose them using this hook. The user-defined settings (weight,
* visible) are automatically applied when entities or entity forms are
* rendered.
*
* @see hook_entity_extra_field_info_alter()
*
* @return array
* The array structure is identical to that of the return value of
* \Drupal\Core\Entity\EntityManagerInterface::getExtraFields().
*/
function hook_entity_extra_field_info() {
$extra = array();
$module_language_enabled = \Drupal::moduleHandler()->moduleExists('language');
$description = t('Node module element');
foreach (node_type_get_types() as $bundle) {
if ($bundle->has_title) {
$extra['node'][$bundle->type]['form']['title'] = array(
'label' => String::checkPlain($bundle->title_label),
'description' => $description,
'weight' => -5,
);
}
// Add also the 'language' select if Language module is enabled and the
// bundle has multilingual support.
// Visibility of the ordering of the language selector is the same as on the
// node/add form.
if ($module_language_enabled) {
$configuration = language_get_default_configuration('node', $bundle->type);
if ($configuration['language_show']) {
$extra['node'][$bundle->type]['form']['language'] = array(
'label' => t('Language'),
'description' => $description,
'weight' => 0,
);
}
}
$extra['node'][$bundle->type]['display']['language'] = array(
'label' => t('Language'),
'description' => $description,
'weight' => 0,
'visible' => FALSE,
);
}
return $extra;
}
/**
* Alter "pseudo-field" components on content entities.
*
* @param array $info
* The array structure is identical to that of the return value of
* \Drupal\Core\Entity\EntityManagerInterface::getExtraFields().
*
* @see hook_entity_extra_field_info()
*/
function hook_entity_extra_field_info_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]['form']['title'])) {
$info['node'][$bundle->type]['form']['title']['weight'] = -20;
}
}
}
......@@ -196,9 +196,9 @@ function entity_test_entity_form_mode_info_alter(&$form_modes) {
}
/**
* Implements hook_field_extra_fields().
* Implements hook_entity_extra_field_info().
*/
function entity_test_field_extra_fields() {
function entity_test_entity_extra_field_info() {
$extra['entity_test']['bundle_with_extra_fields'] = array(
'display' => array(
// Note: those extra fields do not currently display anything, they are
......
......@@ -262,7 +262,7 @@ function hook_taxonomy_term_delete(Drupal\taxonomy\Term $term) {
function hook_taxonomy_term_view(\Drupal\taxonomy\Entity\Term $term, \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display, $view_mode, $langcode) {
// Only do the extra work if the component is configured to be displayed.
// This assumes a 'mymodule_addition' extra field has been defined for the
// vocabulary in hook_field_extra_fields().
// vocabulary in hook_entity_extra_field_info().
if ($display->getComponent('mymodule_addition')) {
$term->content['mymodule_addition'] = array(
'#markup' => mymodule_addition($term),
......
......@@ -135,9 +135,9 @@ function taxonomy_term_uri($term) {
}
/**
* Implements hook_field_extra_fields().
* Implements hook_entity_extra_field_info().
*/
function taxonomy_field_extra_fields() {
function taxonomy_entity_extra_field_info() {
$return = array();
foreach (entity_get_bundles('taxonomy_term') as $bundle => $bundle_info) {
$return['taxonomy_term'][$bundle] = array(
......
......@@ -324,7 +324,7 @@ function hook_user_logout($account) {
function hook_user_view(\Drupal\user\UserInterface $account, \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display, $view_mode, $langcode) {
// Only do the extra work if the component is configured to be displayed.
// This assumes a 'mymodule_addition' extra field has been defined for the
// user entity type in hook_field_extra_fields().
// user entity type in hook_entity_extra_field_info().
if ($display->getComponent('mymodule_addition')) {
$account->content['mymodule_addition'] = array(
'#markup' => mymodule_addition($account),
......
......@@ -205,9 +205,9 @@ function user_picture_enabled() {
}
/**
* Implements hook_field_extra_fields().
* Implements hook_entity_extra_field_info().
*/
function user_field_extra_fields() {
function user_entity_extra_field_info() {
$fields['user']['user']['form']['account'] = array(
'label' => t('User name and password'),
'description' => t('User module account form elements.'),
......
......@@ -816,6 +816,58 @@ public function testGetTranslationFromContext() {
$this->assertSame($translated_entity, $this->entityManager->getTranslationFromContext($entity, 'custom_langcode'));
}
/**
* @covers ::getExtraFields
*/
function testgetExtraFields() {
$this->setUpEntityManager();
$entity_type_id = $this->randomName();
$bundle = $this->randomName();
$language_code = 'en';
$hook_bundle_extra_fields = array(
$entity_type_id => array(
$bundle => array(
'form' => array(
'foo_extra_field' => array(
'label' => 'Foo',
),
),
),
),
);
$processed_hook_bundle_extra_fields = $hook_bundle_extra_fields;
$processed_hook_bundle_extra_fields[$entity_type_id][$bundle] += array(
'display' => array(),
);
$cache_id = 'entity_bundle_extra_fields:' . $entity_type_id . ':' . $bundle . ':' . $language_code;
$language = new Language();
$language->id = $language_code;
$this->languageManager->expects($this->once())
->method('getCurrentLanguage')
->will($this->returnValue($language));
$this->cache->expects($this->once())
->method('get')
->with($cache_id);
$this->moduleHandler->expects($this->once())
->method('invokeAll')
->with('entity_extra_field_info')
->will($this->returnValue($hook_bundle_extra_fields));
$this->moduleHandler->expects($this->once())
->method('alter')
->with('entity_extra_field_info', $hook_bundle_extra_fields);
$this->cache->expects($this->once())
->method('set')
->with($cache_id, $processed_hook_bundle_extra_fields[$entity_type_id][$bundle]);
$this->assertSame($processed_hook_bundle_extra_fields[$entity_type_id][$bundle], $this->entityManager->getExtraFields($entity_type_id, $bundle));
}
/**
* Gets a mock controller class name.
*
......
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