Update to drupal 8.0.0-rc1. For more information, see https://www.drupal.org/node/2582663

This commit is contained in:
Greg Anderson 2015-10-08 11:40:12 -07:00
parent eb34d130a8
commit f32e58e4b1
8476 changed files with 211648 additions and 170042 deletions

View file

@ -6,7 +6,7 @@
*/
namespace Drupal\Core\Entity\Annotation;
use Drupal\Core\StringTranslation\TranslationWrapper;
use Drupal\Core\StringTranslation\TranslatableMarkup;
/**
* Defines a config entity type annotation object.
@ -37,7 +37,7 @@ class ConfigEntityType extends EntityType {
* {@inheritdoc}
*/
public function get() {
$this->definition['group_label'] = new TranslationWrapper('Configuration', array(), array('context' => 'Entity type group'));
$this->definition['group_label'] = new TranslatableMarkup('Configuration', array(), array('context' => 'Entity type group'));
return parent::get();
}

View file

@ -6,7 +6,7 @@
*/
namespace Drupal\Core\Entity\Annotation;
use Drupal\Core\StringTranslation\TranslationWrapper;
use Drupal\Core\StringTranslation\TranslatableMarkup;
/**
* Defines a content entity type annotation object.
@ -37,7 +37,7 @@ class ContentEntityType extends EntityType {
* {@inheritdoc}
*/
public function get() {
$this->definition['group_label'] = new TranslationWrapper('Content', array(), array('context' => 'Entity type group'));
$this->definition['group_label'] = new TranslatableMarkup('Content', array(), array('context' => 'Entity type group'));
return parent::get();
}

View file

@ -19,8 +19,6 @@ use Drupal\Component\Annotation\Plugin;
*
* @see \Drupal\Core\Entity\EntityReferenceSelection\SelectionPluginManager
* @see \Drupal\Core\Entity\EntityReferenceSelection\SelectionInterface
* @see \Drupal\Core\Entity\Plugin\EntityReferenceSelection\SelectionBase
* @see \Drupal\Core\Entity\Plugin\Derivative\SelectionBase
* @see plugin_api
*
* @Annotation

View file

@ -0,0 +1,41 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\BundleEntityFormBase.
*/
namespace Drupal\Core\Entity;
/**
* Class BundleEntityFormBase is a base form for bundle config entities.
*/
class BundleEntityFormBase extends EntityForm {
/**
* Protects the bundle entity's ID property's form element against changes.
*
* This method is assumed to be called on a completely built entity form,
* including a form element for the bundle config entity's ID property.
*
* @param array $form
* The completely built entity bundle form array.
*
* @return array
* The updated entity bundle form array.
*/
protected function protectBundleIdElement(array $form) {
$entity = $this->getEntity();
$id_key = $entity->getEntityType()->getKey('id');
assert('isset($form[$id_key])');
$element = &$form[$id_key];
// Make sure the element is not accidentally re-enabled if it has already
// been disabled.
if (empty($element['#disabled'])) {
$element['#disabled'] = !$entity->isNew();
}
return $form;
}
}

View file

@ -70,7 +70,7 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
/**
* Local cache for the available language objects.
*
* @var array
* @var \Drupal\Core\Language\LanguageInterface[]
*/
protected $languages;
@ -138,7 +138,7 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
protected $isDefaultRevision = TRUE;
/**
* Holds translatable entity keys such as the ID, bundle and revision ID.
* Holds untranslatable entity keys such as the ID, bundle, and revision ID.
*
* @var array
*/
@ -593,7 +593,7 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
}
return $this->entityManager()
->getAccessControlHandler($this->entityTypeId)
->access($this, $operation, $this->activeLangcode, $account, $return_as_object);
->access($this, $operation, $account, $return_as_object);
}
/**
@ -737,22 +737,11 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
if (isset($this->translations[$langcode]['entity'])) {
$translation = $this->translations[$langcode]['entity'];
}
else {
if (isset($this->translations[$langcode])) {
$translation = $this->initializeTranslation($langcode);
$this->translations[$langcode]['entity'] = $translation;
}
else {
// If we were given a valid language and there is no translation for it,
// we return a new one.
$this->getLanguages();
if (isset($this->languages[$langcode])) {
// If the entity or the requested language is not a configured
// language, we fall back to the entity itself, since in this case it
// cannot have translations.
$translation = !$this->languages[$this->defaultLangcode]->isLocked() && !$this->languages[$langcode]->isLocked() ? $this->addTranslation($langcode) : $this;
}
}
// Otherwise if an existing translation language was specified we need to
// instantiate the related translation.
elseif (isset($this->translations[$langcode])) {
$translation = $this->initializeTranslation($langcode);
$this->translations[$langcode]['entity'] = $translation;
}
if (empty($translation)) {
@ -823,10 +812,15 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
* {@inheritdoc}
*/
public function addTranslation($langcode, array $values = array()) {
// Make sure we do not attempt to create a translation if an invalid
// language is specified or the entity cannot be translated.
$this->getLanguages();
if (!isset($this->languages[$langcode]) || $this->hasTranslation($langcode)) {
if (!isset($this->languages[$langcode]) || $this->hasTranslation($langcode) || $this->languages[$langcode]->isLocked()) {
throw new \InvalidArgumentException("Invalid translation language ($langcode) specified.");
}
if ($this->languages[$this->defaultLangcode]->isLocked()) {
throw new \InvalidArgumentException("The entity cannot be translated since it is language neutral ({$this->defaultLangcode}).");
}
// Instantiate a new empty entity so default values will be populated in the
// specified language.

View file

@ -62,6 +62,15 @@ class ContentEntityForm extends EntityForm implements ContentEntityFormInterface
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
parent::submitForm($form, $form_state);
// Update the changed timestamp of the entity.
$this->updateChangedTime($this->entity);
}
/**
* {@inheritdoc}
*/
@ -164,7 +173,7 @@ class ContentEntityForm extends EntityForm implements ContentEntityFormInterface
// language.
$this->initFormLangcodes($form_state);
$langcode = $this->getFormLangcode($form_state);
$this->entity = $this->entity->getTranslation($langcode);
$this->entity = $this->entity->hasTranslation($langcode) ? $this->entity->getTranslation($langcode) : $this->entity->addTranslation($langcode);
$form_display = EntityFormDisplay::collectRenderDisplay($this->entity, $this->getOperation());
$this->setFormDisplay($form_display, $form_state);
@ -269,4 +278,18 @@ class ContentEntityForm extends EntityForm implements ContentEntityFormInterface
}
}
/**
* Updates the changed time of the entity.
*
* Applies only if the entity implements the EntityChangedInterface.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity updated with the submitted values.
*/
public function updateChangedTime(EntityInterface $entity) {
if ($entity->getEntityType()->isSubclassOf(EntityChangedInterface::class)) {
$entity->setChangedTime(REQUEST_TIME);
}
}
}

View file

@ -336,7 +336,7 @@ abstract class ContentEntityStorageBase extends EntityStorageBase implements Dyn
$this->invokeHook('translation_insert', $entity->getTranslation($langcode));
}
elseif (!isset($translations[$langcode]) && isset($original_translations[$langcode])) {
$this->invokeHook('translation_delete', $entity->getTranslation($langcode));
$this->invokeHook('translation_delete', $entity->original->getTranslation($langcode));
}
}
}

View file

@ -91,17 +91,14 @@ class EntityViewController implements ContainerInjectionInterface {
* @param string $view_mode
* (optional) The view mode that should be used to display the entity.
* Defaults to 'full'.
* @param string $langcode
* (optional) For which language the entity should be rendered, defaults to
* the current content language.
*
* @return array
* A render array as expected by drupal_render().
*/
public function view(EntityInterface $_entity, $view_mode = 'full', $langcode = NULL) {
public function view(EntityInterface $_entity, $view_mode = 'full') {
$page = $this->entityManager
->getViewBuilder($_entity->getEntityTypeId())
->view($_entity, $view_mode, $langcode);
->view($_entity, $view_mode);
$page['#pre_render'][] = [$this, 'buildTitle'];
$page['#entity_type'] = $_entity->getEntityTypeId();

View file

@ -155,7 +155,7 @@ class EntityAutocomplete extends Textfield {
if ($match === NULL) {
// Try to get a match from the input string when the user didn't use
// the autocomplete but filled in a value manually.
$match = $handler->validateAutocompleteInput($input, $element, $form_state, $complete_form, !$autocreate);
$match = static::matchEntityByTitle($handler, $input, $element, $form_state, !$autocreate);
}
if ($match !== NULL) {
@ -202,6 +202,60 @@ class EntityAutocomplete extends Textfield {
$form_state->setValueForElement($element, $value);
}
/**
* Finds an entity from an autocomplete input without an explicit ID.
*
* The method will return an entity ID if one single entity unambuguously
* matches the incoming input, and sill assign form errors otherwise.
*
* @param string $input
* Single string from autocomplete element.
* @param array $element
* The form element to set a form error.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current form state.
* @param bool $strict
* Whether to trigger a form error if an element from $input (eg. an entity)
* is not found.
*
* @return integer|null
* Value of a matching entity ID, or NULL if none.
*/
protected static function matchEntityByTitle($handler, $input, &$element, FormStateInterface $form_state, $strict) {
$entities_by_bundle = $handler->getReferenceableEntities($input, '=', 6);
$entities = array_reduce($entities_by_bundle, function ($flattened, $bundle_entities) {
return $flattened + $bundle_entities;
}, []);
$params = array(
'%value' => $input,
'@value' => $input,
);
if (empty($entities)) {
if ($strict) {
// Error if there are no entities available for a required field.
$form_state->setError($element, t('There are no entities matching "%value".', $params));
}
}
elseif (count($entities) > 5) {
$params['@id'] = key($entities);
// Error if there are more than 5 matching entities.
$form_state->setError($element, t('Many entities are called %value. Specify the one you want by appending the id in parentheses, like "@value (@id)".', $params));
}
elseif (count($entities) > 1) {
// More helpful error if there are only a few matching entities.
$multiples = array();
foreach ($entities as $id => $name) {
$multiples[] = $name . ' (' . $id . ')';
}
$params['@id'] = $id;
$form_state->setError($element, t('Multiple entities match this reference; "%multiple". Specify the one you want by appending the id in parentheses, like "@value (@id)".', array('%multiple' => implode('", "', $multiples))));
}
else {
// Take the one and only matching entity.
return key($entities);
}
}
/**
* Converts an array of entity objects into a string of entity labels.
*

View file

@ -311,9 +311,9 @@ abstract class Entity implements EntityInterface {
->getAccessControlHandler($this->entityTypeId)
->createAccess($this->bundle(), $account, [], $return_as_object);
}
return $this->entityManager()
return $this->entityManager()
->getAccessControlHandler($this->entityTypeId)
->access($this, $operation, LanguageInterface::LANGCODE_DEFAULT, $account, $return_as_object);
->access($this, $operation, $account, $return_as_object);
}
/**

View file

@ -181,6 +181,7 @@ class EntityViewDisplay extends EntityDisplayBase implements EntityViewDisplayIn
*/
public function postSave(EntityStorageInterface $storage, $update = TRUE) {
// Reset the render cache for the target entity type.
parent::postSave($storage, $update);
if (\Drupal::entityManager()->hasHandler($this->targetEntityType, 'view_builder')) {
\Drupal::entityManager()->getViewBuilder($this->targetEntityType)->resetCache();
}
@ -248,7 +249,7 @@ class EntityViewDisplay extends EntityDisplayBase implements EntityViewDisplayIn
$items = $grouped_items[$id];
/** @var \Drupal\Core\Access\AccessResultInterface $field_access */
$field_access = $items->access('view', NULL, TRUE);
$build_list[$id][$name] = $field_access->isAllowed() ? $formatter->view($items) : [];
$build_list[$id][$name] = $field_access->isAllowed() ? $formatter->view($items, $entity->language()->getId()) : [];
// Apply the field access cacheability metadata to the render array.
$this->renderer->addCacheableDependency($build_list[$id][$name], $field_access);
}

View file

@ -53,8 +53,9 @@ class EntityAccessControlHandler extends EntityHandlerBase implements EntityAcce
/**
* {@inheritdoc}
*/
public function access(EntityInterface $entity, $operation, $langcode = LanguageInterface::LANGCODE_DEFAULT, AccountInterface $account = NULL, $return_as_object = FALSE) {
public function access(EntityInterface $entity, $operation, AccountInterface $account = NULL, $return_as_object = FALSE) {
$account = $this->prepareUser($account);
$langcode = $entity->language()->getId();
if (($return = $this->getCache($entity->uuid(), $operation, $langcode, $account)) !== NULL) {
// Cache hit, no work necessary.
@ -71,8 +72,8 @@ class EntityAccessControlHandler extends EntityHandlerBase implements EntityAcce
// - No modules say to deny access.
// - At least one module says to grant access.
$access = array_merge(
$this->moduleHandler()->invokeAll('entity_access', array($entity, $operation, $account, $langcode)),
$this->moduleHandler()->invokeAll($entity->getEntityTypeId() . '_access', array($entity, $operation, $account, $langcode))
$this->moduleHandler()->invokeAll('entity_access', [$entity, $operation, $account]),
$this->moduleHandler()->invokeAll($entity->getEntityTypeId() . '_access', [$entity, $operation, $account])
);
$return = $this->processAccessHookResults($access);
@ -80,7 +81,7 @@ class EntityAccessControlHandler extends EntityHandlerBase implements EntityAcce
// Also execute the default access check except when the access result is
// already forbidden, as in that case, it can not be anything else.
if (!$return->isForbidden()) {
$return = $return->orIf($this->checkAccess($entity, $operation, $langcode, $account));
$return = $return->orIf($this->checkAccess($entity, $operation, $account));
}
$result = $this->setCache($return, $entity->uuid(), $operation, $langcode, $account);
return $return_as_object ? $result : $result->isAllowed();
@ -124,15 +125,13 @@ class EntityAccessControlHandler extends EntityHandlerBase implements EntityAcce
* The entity for which to check access.
* @param string $operation
* The entity operation. Usually one of 'view', 'update' or 'delete'.
* @param string $langcode
* The language code for which to check access.
* @param \Drupal\Core\Session\AccountInterface $account
* The user for which to check access.
*
* @return \Drupal\Core\Access\AccessResultInterface
* The access result.
*/
protected function checkAccess(EntityInterface $entity, $operation, $langcode, AccountInterface $account) {
protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) {
if ($operation == 'delete' && $entity->isNew()) {
return AccessResult::forbidden()->cacheUntilEntityChanges($entity);
}

View file

@ -29,9 +29,6 @@ interface EntityAccessControlHandlerInterface {
* @param string $operation
* The operation access should be checked for.
* Usually one of "view", "update" or "delete".
* @param string $langcode
* (optional) The language code for which to check access. Defaults to
* LanguageInterface::LANGCODE_DEFAULT.
* @param \Drupal\Core\Session\AccountInterface $account
* (optional) The user session for which to check access, or NULL to check
* access for the current user. Defaults to NULL.
@ -45,7 +42,7 @@ interface EntityAccessControlHandlerInterface {
* returned, i.e. TRUE means access is explicitly allowed, FALSE means
* access is either explicitly forbidden or "no opinion".
*/
public function access(EntityInterface $entity, $operation, $langcode = LanguageInterface::LANGCODE_DEFAULT, AccountInterface $account = NULL, $return_as_object = FALSE);
public function access(EntityInterface $entity, $operation, AccountInterface $account = NULL, $return_as_object = FALSE);
/**
* Checks access to create an entity.

View file

@ -8,7 +8,7 @@
namespace Drupal\Core\Entity;
/**
* An interface for reacting to entity bundle creation, deletion, and renames.
* An interface for reacting to entity bundle creation and deletion.
*
* @todo Convert to Symfony events: https://www.drupal.org/node/2332935
*/
@ -24,20 +24,6 @@ interface EntityBundleListenerInterface {
*/
public function onBundleCreate($bundle, $entity_type_id);
/**
* Reacts to a bundle being renamed.
*
* This method runs before fields are updated with the new bundle name.
*
* @param string $bundle
* The name of the bundle being renamed.
* @param string $bundle_new
* The new name of the bundle.
* @param string $entity_type_id
* The entity type to which the bundle is bound; e.g. 'node' or 'user'.
*/
public function onBundleRename($bundle, $bundle_new, $entity_type_id);
/**
* Reacts to a bundle being deleted.
*

View file

@ -29,6 +29,16 @@ interface EntityChangedInterface {
*/
public function getChangedTime();
/**
* Sets the timestamp of the last entity change for the current translation.
*
* @param int $timestamp
* The timestamp of the last entity save operation.
*
* @return $this
*/
public function setChangedTime($timestamp);
/**
* Gets the timestamp of the last entity change across all translations.
*

View file

@ -28,4 +28,26 @@ trait EntityChangedTrait {
return $changed;
}
/**
* Gets the timestamp of the last entity change for the current translation.
*
* @return int
* The timestamp of the last entity save operation.
*/
public function getChangedTime() {
return $this->get('changed')->value;
}
/**
* Sets the timestamp of the last entity change for the current translation.
*
* @param int $timestamp
* The timestamp of the last entity save operation.
*
* @return $this
*/
public function setChangedTime($timestamp) {
$this->set('changed', $timestamp);
return $this;
}
}

View file

@ -34,7 +34,7 @@ class EntityDeleteForm extends EntityConfirmFormBase {
if (!($entity instanceof ConfigEntityInterface)) {
return $form;
}
$this->addDependencyListsToForm($form, $entity->getConfigDependencyKey(), [$entity->getConfigDependencyName()], $this->getConfigManager(), $this->entityManager);
$this->addDependencyListsToForm($form, $entity->getConfigDependencyKey(), $this->getConfigNamesToDelete($entity), $this->getConfigManager(), $this->entityManager);
return $form;
}
@ -49,4 +49,17 @@ class EntityDeleteForm extends EntityConfirmFormBase {
return \Drupal::service('config.manager');
}
/**
* Returns config names to delete for the deletion confirmation form.
*
* @param \Drupal\Core\Config\Entity\ConfigEntityInterface $entity
* The entity being deleted.
*
* @return string[]
* A list of configuration names that will be deleted by this form.
*/
protected function getConfigNamesToDelete(ConfigEntityInterface $entity) {
return [$entity->getConfigDependencyName()];
}
}

View file

@ -278,7 +278,7 @@ abstract class EntityDisplayBase extends ConfigEntityBase implements EntityDispl
$mode_entity = $this->entityManager()->getStorage('entity_' . $this->displayContext . '_mode')->load($target_entity_type->id() . '.' . $this->mode);
$this->addDependency('config', $mode_entity->getConfigDependencyName());
}
return $this->dependencies;
return $this;
}
/**
@ -428,18 +428,87 @@ abstract class EntityDisplayBase extends ConfigEntityBase implements EntityDispl
}
}
foreach ($this->getComponents() as $name => $component) {
if (isset($component['type']) && $definition = $this->pluginManager->getDefinition($component['type'], FALSE)) {
if (in_array($definition['provider'], $dependencies['module'])) {
if ($renderer = $this->getRenderer($name)) {
if (in_array($renderer->getPluginDefinition()['provider'], $dependencies['module'])) {
// Revert to the defaults if the plugin that supplies the widget or
// formatter depends on a module that is being uninstalled.
$this->setComponent($name);
$changed = TRUE;
}
// Give this component the opportunity to react on dependency removal.
$component_removed_dependencies = $this->getPluginRemovedDependencies($renderer->calculateDependencies(), $dependencies);
if ($component_removed_dependencies) {
if ($renderer->onDependencyRemoval($component_removed_dependencies)) {
// Update component settings to reflect changes.
$component['settings'] = $renderer->getSettings();
$component['third_party_settings'] = [];
foreach ($renderer->getThirdPartyProviders() as $module) {
$component['third_party_settings'][$module] = $renderer->getThirdPartySettings($module);
}
$this->setComponent($name, $component);
$changed = TRUE;
}
// If there are unresolved deleted dependencies left, disable this
// component to avoid the removal of the entire display entity.
if ($this->getPluginRemovedDependencies($renderer->calculateDependencies(), $dependencies)) {
$this->removeComponent($name);
$arguments = [
'@display' => (string) $this->getEntityType()->getLabel(),
'@id' => $this->id(),
'@name' => $name,
];
$this->getLogger()->warning("@display '@id': Component '@name' was disabled because its settings depend on removed dependencies.", $arguments);
$changed = TRUE;
}
}
}
}
return $changed;
}
/**
* Returns the plugin dependencies being removed.
*
* The function recursively computes the intersection between all plugin
* dependencies and all removed dependencies.
*
* Note: The two arguments do not have the same structure.
*
* @param array[] $plugin_dependencies
* A list of dependencies having the same structure as the return value of
* ConfigEntityInterface::calculateDependencies().
* @param array[] $removed_dependencies
* A list of dependencies having the same structure as the input argument of
* ConfigEntityInterface::onDependencyRemoval().
*
* @return array
* A recursively computed intersection.
*
* @see \Drupal\Core\Config\Entity\ConfigEntityInterface::calculateDependencies()
* @see \Drupal\Core\Config\Entity\ConfigEntityInterface::onDependencyRemoval()
*/
protected function getPluginRemovedDependencies(array $plugin_dependencies, array $removed_dependencies) {
$intersect = [];
foreach ($plugin_dependencies as $type => $dependencies) {
if ($removed_dependencies[$type]) {
// Config and content entities have the dependency names as keys while
// module and theme dependencies are indexed arrays of dependency names.
// @see \Drupal\Core\Config\ConfigManager::callOnDependencyRemoval()
if (in_array($type, ['config', 'content'])) {
$removed = array_intersect_key($removed_dependencies[$type], array_flip($dependencies));
}
else {
$removed = array_values(array_intersect($removed_dependencies[$type], $dependencies));
}
if ($removed) {
$intersect[$type] = $removed;
}
}
}
return $intersect;
}
/**
* {@inheritdoc}
*/
@ -471,4 +540,14 @@ abstract class EntityDisplayBase extends ConfigEntityBase implements EntityDispl
$this->__construct($values, $this->entityTypeId);
}
/**
* Provides the 'system' channel logger service.
*
* @return \Psr\Log\LoggerInterface
* The 'system' channel logger.
*/
protected function getLogger() {
return \Drupal::logger('system');
}
}

View file

@ -93,7 +93,7 @@ abstract class EntityDisplayModeBase extends ConfigEntityBase implements EntityD
parent::calculateDependencies();
$target_entity_type = \Drupal::entityManager()->getDefinition($this->targetEntityType);
$this->addDependency('module', $target_entity_type->getProvider());
return $this->dependencies;
return $this;
}
/**

View file

@ -240,10 +240,19 @@ interface EntityInterface extends AccessibleInterface, CacheableDependencyInterf
/**
* Acts on an entity before the presave hook is invoked.
*
* Used before the entity is saved and before invoking the presave hook.
* Used before the entity is saved and before invoking the presave hook. Note
* that in case of translatable content entities this callback is only fired
* on their current translation. It is up to the developer to iterate
* over all translations if needed. This is different from its counterpart in
* the Field API, FieldItemListInterface::preSave(), which is fired on all
* field translations automatically.
* @todo Adjust existing implementations and the documentation according to
* https://www.drupal.org/node/2577609 to have a consistent API.
*
* @param \Drupal\Core\Entity\EntityStorageInterface $storage
* The entity storage object.
*
* @see \Drupal\Core\Field\FieldItemListInterface::preSave()
*/
public function preSave(EntityStorageInterface $storage);
@ -251,7 +260,9 @@ interface EntityInterface extends AccessibleInterface, CacheableDependencyInterf
* Acts on a saved entity before the insert or update hook is invoked.
*
* Used after the entity is saved, but before invoking the insert or update
* hook.
* hook. Note that in case of translatable content entities this callback is
* only fired on their current translation. It is up to the developer to
* iterate over all translations if needed.
*
* @param \Drupal\Core\Entity\EntityStorageInterface $storage
* The entity storage object.

View file

@ -228,6 +228,7 @@ class EntityListBuilder extends EntityHandlerBase implements EntityListBuilderIn
'#empty' => $this->t('There is no @label yet.', array('@label' => $this->entityType->getLabel())),
'#cache' => [
'contexts' => $this->entityType->getListCacheContexts(),
'tags' => $this->entityType->getListCacheTags(),
],
);
foreach ($this->load() as $entity) {

View file

@ -32,7 +32,7 @@ use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslationInterface;
use Drupal\Core\TypedData\TranslatableInterface;
use Drupal\Core\TypedData\TypedDataManager;
use Drupal\Core\TypedData\TypedDataManagerInterface;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
@ -111,7 +111,7 @@ class EntityManager extends DefaultPluginManager implements EntityManagerInterfa
/**
* The typed data manager.
*
* @var \Drupal\Core\TypedData\TypedDataManager
* @var \Drupal\Core\TypedData\TypedDataManagerInterface
*/
protected $typedDataManager;
@ -193,14 +193,14 @@ class EntityManager extends DefaultPluginManager implements EntityManagerInterfa
* The string translationManager.
* @param \Drupal\Core\DependencyInjection\ClassResolverInterface $class_resolver
* The class resolver.
* @param \Drupal\Core\TypedData\TypedDataManager $typed_data_manager
* @param \Drupal\Core\TypedData\TypedDataManagerInterface $typed_data_manager
* The typed data manager.
* @param \Drupal\Core\KeyValueStore\KeyValueFactoryInterface $key_value_factory
* The keyvalue factory.
* @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
* The event dispatcher.
*/
public function __construct(\Traversable $namespaces, ModuleHandlerInterface $module_handler, CacheBackendInterface $cache, LanguageManagerInterface $language_manager, TranslationInterface $translation_manager, ClassResolverInterface $class_resolver, TypedDataManager $typed_data_manager, KeyValueFactoryInterface $key_value_factory, EventDispatcherInterface $event_dispatcher) {
public function __construct(\Traversable $namespaces, ModuleHandlerInterface $module_handler, CacheBackendInterface $cache, LanguageManagerInterface $language_manager, TranslationInterface $translation_manager, ClassResolverInterface $class_resolver, TypedDataManagerInterface $typed_data_manager, KeyValueFactoryInterface $key_value_factory, EventDispatcherInterface $event_dispatcher) {
parent::__construct('Entity', $namespaces, $module_handler, 'Drupal\Core\Entity\EntityInterface');
$this->setCacheBackend($cache, 'entity_type', array('entity_types'));
@ -945,7 +945,7 @@ class EntityManager extends DefaultPluginManager implements EntityManagerInterfa
foreach ($definitions as $entity_type_id => $definition) {
if ($group) {
$options[$definition->getGroupLabel()][$entity_type_id] = $definition->getLabel();
$options[(string) $definition->getGroupLabel()][$entity_type_id] = $definition->getLabel();
}
else {
$options[$entity_type_id] = $definition->getLabel();
@ -960,7 +960,7 @@ class EntityManager extends DefaultPluginManager implements EntityManagerInterfa
// Make sure that the 'Content' group is situated at the top.
$content = $this->t('Content', array(), array('context' => 'Entity type group'));
$options = array($content => $options[$content]) + $options;
$options = array((string) $content => $options[(string) $content]) + $options;
}
return $options;
@ -1088,15 +1088,29 @@ class EntityManager extends DefaultPluginManager implements EntityManagerInterfa
/**
* {@inheritdoc}
*/
public function getViewModeOptions($entity_type, $include_disabled = FALSE) {
return $this->getDisplayModeOptions('view_mode', $entity_type, $include_disabled);
public function getViewModeOptions($entity_type_id) {
return $this->getDisplayModeOptions('view_mode', $entity_type_id);
}
/**
* {@inheritdoc}
*/
public function getFormModeOptions($entity_type, $include_disabled = FALSE) {
return $this->getDisplayModeOptions('form_mode', $entity_type, $include_disabled);
public function getFormModeOptions($entity_type_id) {
return $this->getDisplayModeOptions('form_mode', $entity_type_id);
}
/**
* {@inheritdoc}
*/
public function getViewModeOptionsByBundle($entity_type_id, $bundle) {
return $this->getDisplayModeOptionsByBundle('view_mode', $entity_type_id, $bundle);
}
/**
* {@inheritdoc}
*/
public function getFormModeOptionsByBundle($entity_type_id, $bundle) {
return $this->getDisplayModeOptionsByBundle('form_mode', $entity_type_id, $bundle);
}
/**
@ -1106,19 +1120,55 @@ class EntityManager extends DefaultPluginManager implements EntityManagerInterfa
* The display type to be retrieved. It can be "view_mode" or "form_mode".
* @param string $entity_type_id
* The entity type whose display mode options should be returned.
* @param bool $include_disabled
* Force to include disabled display modes. Defaults to FALSE.
*
* @return array
* An array of display mode labels, keyed by the display mode ID.
*/
protected function getDisplayModeOptions($display_type, $entity_type_id, $include_disabled = FALSE) {
protected function getDisplayModeOptions($display_type, $entity_type_id) {
$options = array('default' => t('Default'));
foreach ($this->getDisplayModesByEntityType($display_type, $entity_type_id) as $mode => $settings) {
if (!empty($settings['status']) || $include_disabled) {
$options[$mode] = $settings['label'];
$options[$mode] = $settings['label'];
}
return $options;
}
/**
* Returns an array of display mode options by bundle.
*
* @param $display_type
* The display type to be retrieved. It can be "view_mode" or "form_mode".
* @param string $entity_type_id
* The entity type whose display mode options should be returned.
* @param string $bundle
* The name of the bundle.
*
* @return array
* An array of display mode labels, keyed by the display mode ID.
*/
protected function getDisplayModeOptionsByBundle($display_type, $entity_type_id, $bundle) {
// Collect all the entity's display modes.
$options = $this->getDisplayModeOptions($display_type, $entity_type_id);
// Filter out modes for which the entity display is disabled
// (or non-existent).
$load_ids = array();
// Get the list of available entity displays for the current bundle.
foreach (array_keys($options) as $mode) {
$load_ids[] = $entity_type_id . '.' . $bundle . '.' . $mode;
}
// Load the corresponding displays.
$displays = $this->getStorage($display_type == 'form_mode' ? 'entity_form_display' : 'entity_view_display')
->loadMultiple($load_ids);
// Unset the display modes that are not active or do not exist.
foreach (array_keys($options) as $mode) {
$display_id = $entity_type_id . '.' . $bundle . '.' . $mode;
if (!isset($displays[$display_id]) || !$displays[$display_id]->status()) {
unset($options[$mode]);
}
}
return $options;
}
@ -1317,31 +1367,6 @@ class EntityManager extends DefaultPluginManager implements EntityManagerInterfa
$this->moduleHandler->invokeAll('entity_bundle_create', array($entity_type_id, $bundle));
}
/**
* {@inheritdoc}
*/
public function onBundleRename($bundle_old, $bundle_new, $entity_type_id) {
$this->clearCachedBundles();
// Notify the entity storage.
$storage = $this->getStorage($entity_type_id);
if ($storage instanceof EntityBundleListenerInterface) {
$storage->onBundleRename($bundle_old, $bundle_new, $entity_type_id);
}
// Rename existing base field bundle overrides.
$overrides = $this->getStorage('base_field_override')->loadByProperties(array('entity_type' => $entity_type_id, 'bundle' => $bundle_old));
foreach ($overrides as $override) {
$override->set('id', $entity_type_id . '.' . $bundle_new . '.' . $override->getName());
$override->set('bundle', $bundle_new);
$override->allowBundleRename();
$override->save();
}
// Invoke hook_entity_bundle_rename() hook.
$this->moduleHandler->invokeAll('entity_bundle_rename', array($entity_type_id, $bundle_old, $bundle_new));
$this->clearCachedFieldDefinitions();
}
/**
* {@inheritdoc}
*/

View file

@ -252,9 +252,9 @@ interface EntityManagerInterface extends PluginManagerInterface, EntityTypeListe
* Creates a new handler instance for a entity type and handler type.
*
* @param string $entity_type
* The entity type for this controller.
* The entity type for this handler.
* @param string $handler_type
* The controller type to create an instance for.
* The handler type to create an instance for.
*
* @return object
* A handler instance.
@ -432,26 +432,48 @@ interface EntityManagerInterface extends PluginManagerInterface, EntityTypeListe
*
* @param string $entity_type_id
* The entity type whose view mode options should be returned.
* @param bool $include_disabled
* Force to include disabled view modes. Defaults to FALSE.
*
* @return array
* An array of view mode labels, keyed by the display mode ID.
*/
public function getViewModeOptions($entity_type_id, $include_disabled = FALSE);
public function getViewModeOptions($entity_type_id);
/**
* Gets an array of form mode options.
*
* @param string $entity_type_id
* The entity type whose form mode options should be returned.
* @param bool $include_disabled
* Force to include disabled form modes. Defaults to FALSE.
*
* @return array
* An array of form mode labels, keyed by the display mode ID.
*/
public function getFormModeOptions($entity_type_id, $include_disabled = FALSE);
public function getFormModeOptions($entity_type_id);
/**
* Returns an array of view mode options by bundle.
*
* @param string $entity_type_id
* The entity type whose view mode options should be returned.
* @param string $bundle
* The name of the bundle.
*
* @return array
* An array of view mode labels, keyed by the display mode ID.
*/
public function getViewModeOptionsByBundle($entity_type_id, $bundle);
/**
* Returns an array of form mode options by bundle.
*
* @param string $entity_type_id
* The entity type whose form mode options should be returned.
* @param string $bundle
* The name of the bundle.
*
* @return array
* An array of form mode labels, keyed by the display mode ID.
*/
public function getFormModeOptionsByBundle($entity_type_id, $bundle);
/**
* Loads an entity by UUID.

View file

@ -8,16 +8,13 @@
namespace Drupal\Core\Entity\EntityReferenceSelection;
use Drupal\Core\Database\Query\SelectInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\PluginFormInterface;
/**
* Interface definition for Entity Reference Selection plugins.
*
* @see \Drupal\Core\Entity\Plugin\EntityReferenceSelection\SelectionBase
* @see \Drupal\Core\Entity\EntityReferenceSelection\SelectionPluginManager
* @see \Drupal\Core\Entity\Annotation\EntityReferenceSelection
* @see \Drupal\Core\Entity\Plugin\Derivative\SelectionBase
* @see plugin_api
*/
interface SelectionInterface extends PluginFormInterface {
@ -27,7 +24,7 @@ interface SelectionInterface extends PluginFormInterface {
*
* @return array
* A nested array of entities, the first level is keyed by the
* entity bundle, which contains an array of entity labels (safe HTML),
* entity bundle, which contains an array of entity labels (escaped),
* keyed by the entity ID.
*/
public function getReferenceableEntities($match = NULL, $match_operator = 'CONTAINS', $limit = 0);
@ -48,28 +45,6 @@ interface SelectionInterface extends PluginFormInterface {
*/
public function validateReferenceableEntities(array $ids);
/**
* Validates input from an autocomplete widget that has no ID.
*
* @param string $input
* Single string from autocomplete widget.
* @param array $element
* The form element to set a form error.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current form state.
* @param array $form
* The form.
* @param bool $strict
* Whether to trigger a form error if an element from $input (eg. an entity)
* is not found. Defaults to TRUE.
*
* @return integer|null
* Value of a matching entity ID, or NULL if none.
*
* @see \Drupal\entity_reference\Plugin\Field\FieldWidget::elementValidate()
*/
public function validateAutocompleteInput($input, &$element, FormStateInterface $form_state, $form, $strict = TRUE);
/**
* Allows the selection to alter the SelectQuery generated by EntityFieldQuery.
*

View file

@ -19,8 +19,6 @@ use Drupal\Core\Plugin\DefaultPluginManager;
*
* @see \Drupal\Core\Entity\Annotation\EntityReferenceSelection
* @see \Drupal\Core\Entity\EntityReferenceSelection\SelectionInterface
* @see \Drupal\Core\Entity\Plugin\EntityReferenceSelection\SelectionBase
* @see \Drupal\Core\Entity\Plugin\Derivative\SelectionBase
* @see plugin_api
*/
class SelectionPluginManager extends DefaultPluginManager implements SelectionPluginManagerInterface, FallbackPluginManagerInterface {

View file

@ -351,20 +351,26 @@ abstract class EntityStorageBase extends EntityHandlerBase implements EntityStor
return;
}
// Ensure that the entities are keyed by ID.
$keyed_entities = [];
foreach ($entities as $entity) {
$keyed_entities[$entity->id()] = $entity;
}
// Allow code to run before deleting.
$entity_class = $this->entityClass;
$entity_class::preDelete($this, $entities);
foreach ($entities as $entity) {
$entity_class::preDelete($this, $keyed_entities);
foreach ($keyed_entities as $entity) {
$this->invokeHook('predelete', $entity);
}
// Perform the delete and reset the static cache for the deleted entities.
$this->doDelete($entities);
$this->resetCache(array_keys($entities));
$this->doDelete($keyed_entities);
$this->resetCache(array_keys($keyed_entities));
// Allow code to run after deleting.
$entity_class::postDelete($this, $entities);
foreach ($entities as $entity) {
$entity_class::postDelete($this, $keyed_entities);
foreach ($keyed_entities as $entity) {
$this->invokeHook('delete', $entity);
}
}

View file

@ -110,7 +110,15 @@ class EntityType implements EntityTypeInterface {
/**
* The name of a callback that returns the label of the entity.
*
* @var string|null
* @var callable|null
*
* @deprecated in Drupal 8.0.x-dev and will be removed before Drupal 9.0.0.
* Use Drupal\Core\Entity\EntityInterface::label() for complex label
* generation as needed.
*
* @see \Drupal\Core\Entity\EntityInterface::label()
*
* @todo Remove usages of label_callback https://www.drupal.org/node/2450793.
*/
protected $label_callback = NULL;
@ -688,7 +696,7 @@ class EntityType implements EntityTypeInterface {
* {@inheritdoc}
*/
public function getLabel() {
return (string) $this->label;
return $this->label;
}
/**
@ -725,7 +733,7 @@ class EntityType implements EntityTypeInterface {
* {@inheritdoc}
*/
public function getGroupLabel() {
return !empty($this->group_label) ? (string) $this->group_label : $this->t('Other', array(), array('context' => 'Entity type group'));
return !empty($this->group_label) ? $this->group_label : $this->t('Other', array(), array('context' => 'Entity type group'));
}
/**

View file

@ -7,6 +7,8 @@
namespace Drupal\Core\Entity;
use Drupal\Component\Plugin\Definition\PluginDefinitionInterface;
/**
* Provides an interface for an entity type and its metadata.
*
@ -15,7 +17,7 @@ namespace Drupal\Core\Entity;
* implemented to alter existing data and fill-in defaults. Module-specific
* properties should be documented in the hook implementations defining them.
*/
interface EntityTypeInterface {
interface EntityTypeInterface extends PluginDefinitionInterface {
/**
* The maximum length of ID, in characters.
@ -66,14 +68,6 @@ interface EntityTypeInterface {
*/
public function getProvider();
/**
* Gets the name of the entity type class.
*
* @return string
* The name of the entity type class.
*/
public function getClass();
/**
* Gets the name of the original entity type class.
*
@ -108,8 +102,8 @@ interface EntityTypeInterface {
* - label: (optional) The name of the property that contains the entity
* label. For example, if the entity's label is located in
* $entity->subject, then 'subject' should be specified here. If complex
* logic is required to build the label, a 'label_callback' should be
* defined instead (see the $label_callback block above for details).
* logic is required to build the label,
* \Drupal\Core\Entity\EntityInterface::label() should be used.
* - langcode: (optional) The name of the property that contains the
* language code. For instance, if the entity's language is located in
* $entity->langcode, then 'langcode' should be specified here.
@ -170,16 +164,6 @@ interface EntityTypeInterface {
*/
public function isPersistentlyCacheable();
/**
* Sets the name of the entity type class.
*
* @param string $class
* The name of the entity type class.
*
* @return $this
*/
public function setClass($class);
/**
* Determines if there is a handler for a given type.
*
@ -493,15 +477,23 @@ interface EntityTypeInterface {
* entity label is the main string associated with an entity; for example, the
* title of a node or the subject of a comment. If there is an entity object
* property that defines the label, use the 'label' element of the
* 'entity_keys' return value component to provide this information (see
* below). If more complex logic is needed to determine the label of an
* entity, you can instead specify a callback function here, which will be
* called to determine the entity label. See also the
* \Drupal\Core\Entity\EntityInterface::label() method, which implements this
* logic.
* 'entity_keys' return value component to provide this information. If more
* complex logic is needed to determine the label of an entity, you can
* instead specify a callback function here, which will be called to determine
* the entity label.
*
* @return callable|null
* The callback, or NULL if none exists.
*
* @deprecated in Drupal 8.0.x-dev and will be removed before Drupal 9.0.0.
* Use Drupal\Core\Entity\EntityInterface::label() for complex label
* generation as needed.
*
* @see \Drupal\Core\Entity\EntityInterface::label()
* @see \Drupal\Core\Entity\EntityTypeInterface::setLabelCallback()
* @see \Drupal\Core\Entity\EntityTypeInterface::hasLabelCallback()
*
* @todo Remove usages of label_callback https://www.drupal.org/node/2450793.
*/
public function getLabelCallback();
@ -512,6 +504,13 @@ interface EntityTypeInterface {
* A callable that returns the label of the entity.
*
* @return $this
*
* @deprecated in Drupal 8.0.x-dev and will be removed before Drupal 9.0.0.
* Use EntityInterface::label() for complex label generation as needed.
*
* @see \Drupal\Core\Entity\EntityInterface::label()
* @see \Drupal\Core\Entity\EntityTypeInterface::getLabelCallback()
* @see \Drupal\Core\Entity\EntityTypeInterface::hasLabelCallback()
*/
public function setLabelCallback($callback);
@ -519,6 +518,13 @@ interface EntityTypeInterface {
* Indicates if a label callback exists.
*
* @return bool
*
* @deprecated in Drupal 8.0.x-dev and will be removed before Drupal 9.0.0.
* Use EntityInterface::label() for complex label generation as needed.
*
* @see \Drupal\Core\Entity\EntityInterface::label()
* @see \Drupal\Core\Entity\EntityTypeInterface::getLabelCallback()
* @see \Drupal\Core\Entity\EntityTypeInterface::setLabelCallback()
*/
public function hasLabelCallback();
@ -645,6 +651,20 @@ interface EntityTypeInterface {
*/
public function setUriCallback($callback);
/**
* Gets the machine name of the entity type group.
*
* @return string
*/
public function getGroup();
/**
* Gets the human-readable name of the entity type group.
*
* @return string
*/
public function getGroupLabel();
/**
* The list cache contexts associated with this entity type.
*

View file

@ -12,10 +12,9 @@ use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\Core\Field\FieldItemInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\TypedData\TranslatableInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\TypedData\TranslatableInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
@ -120,7 +119,6 @@ class EntityViewBuilder extends EntityHandlerBase implements EntityHandlerInterf
$build_list = array(
'#sorted' => TRUE,
'#pre_render' => array(array($this, 'buildMultiple')),
'#langcode' => $langcode ?: $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)->getId(),
);
$weight = 0;
foreach ($entities as $key => $entity) {
@ -129,10 +127,9 @@ class EntityViewBuilder extends EntityHandlerBase implements EntityHandlerInterf
$entity = $this->entityManager->getTranslationFromContext($entity, $langcode);
// Set build defaults.
$entity_langcode = $entity->language()->getId();
$build_list[$key] = $this->getBuildDefaults($entity, $view_mode, $entity_langcode);
$build_list[$key] = $this->getBuildDefaults($entity, $view_mode);
$entityType = $this->entityTypeId;
$this->moduleHandler()->alter(array($entityType . '_build_defaults', 'entity_build_defaults'), $build_list[$key], $entity, $view_mode, $entity_langcode);
$this->moduleHandler()->alter(array($entityType . '_build_defaults', 'entity_build_defaults'), $build_list[$key], $entity, $view_mode);
$build_list[$key]['#weight'] = $weight++;
}
@ -147,22 +144,18 @@ class EntityViewBuilder extends EntityHandlerBase implements EntityHandlerInterf
* The entity for which the defaults should be provided.
* @param string $view_mode
* The view mode that should be used.
* @param string $langcode
* For which language the entity should be prepared, defaults to
* the current content language.
*
* @return array
*/
protected function getBuildDefaults(EntityInterface $entity, $view_mode, $langcode) {
protected function getBuildDefaults(EntityInterface $entity, $view_mode) {
// Allow modules to change the view mode.
$context = array('langcode' => $langcode);
$context = [];
$this->moduleHandler()->alter('entity_view_mode', $view_mode, $entity, $context);
$build = array(
'#theme' => $this->entityTypeId,
"#{$this->entityTypeId}" => $entity,
'#view_mode' => $view_mode,
'#langcode' => $langcode,
// Collect cache defaults for this entity.
'#cache' => array(
'tags' => Cache::mergeTags($this->getCacheTags(), $entity->getCacheTags()),
@ -185,7 +178,7 @@ class EntityViewBuilder extends EntityHandlerBase implements EntityHandlerInterf
);
if ($entity instanceof TranslatableInterface && count($entity->getTranslationLanguages()) > 1) {
$build['#cache']['keys'][] = $langcode;
$build['#cache']['keys'][] = $entity->language()->getId();
}
}
@ -211,10 +204,7 @@ class EntityViewBuilder extends EntityHandlerBase implements EntityHandlerInterf
* @see drupal_render()
*/
public function build(array $build) {
$build_list = array(
'#langcode' => $build['#langcode'],
);
$build_list[] = $build;
$build_list = [$build];
$build_list = $this->buildMultiple($build_list);
return $build_list[0];
}
@ -240,7 +230,6 @@ class EntityViewBuilder extends EntityHandlerBase implements EntityHandlerInterf
public function buildMultiple(array $build_list) {
// Build the view modes and display objects.
$view_modes = array();
$langcode = $build_list['#langcode'];
$entity_type_key = "#{$this->entityTypeId}";
$view_hook = "{$this->entityTypeId}_view";
@ -259,16 +248,16 @@ class EntityViewBuilder extends EntityHandlerBase implements EntityHandlerInterf
// Build content for the displays represented by the entities.
foreach ($view_modes as $view_mode => $view_mode_entities) {
$displays = EntityViewDisplay::collectRenderDisplays($view_mode_entities, $view_mode);
$this->buildComponents($build_list, $view_mode_entities, $displays, $view_mode, $langcode);
$this->buildComponents($build_list, $view_mode_entities, $displays, $view_mode);
foreach (array_keys($view_mode_entities) as $key) {
// Allow for alterations while building, before rendering.
$entity = $build_list[$key][$entity_type_key];
$display = $displays[$entity->bundle()];
$this->moduleHandler()->invokeAll($view_hook, array(&$build_list[$key], $entity, $display, $view_mode, $langcode));
$this->moduleHandler()->invokeAll('entity_view', array(&$build_list[$key], $entity, $display, $view_mode, $langcode));
$this->moduleHandler()->invokeAll($view_hook, [&$build_list[$key], $entity, $display, $view_mode]);
$this->moduleHandler()->invokeAll('entity_view', [&$build_list[$key], $entity, $display, $view_mode]);
$this->alterBuild($build_list[$key], $entity, $display, $view_mode, $langcode);
$this->alterBuild($build_list[$key], $entity, $display, $view_mode);
// Assign the weights configured in the display.
// @todo: Once https://www.drupal.org/node/1875974 provides the missing
@ -291,7 +280,7 @@ class EntityViewBuilder extends EntityHandlerBase implements EntityHandlerInterf
/**
* {@inheritdoc}
*/
public function buildComponents(array &$build, array $entities, array $displays, $view_mode, $langcode = NULL) {
public function buildComponents(array &$build, array $entities, array $displays, $view_mode) {
$entities_by_bundle = array();
foreach ($entities as $id => $entity) {
// Initialize the field item attributes for the fields being displayed.
@ -335,11 +324,8 @@ class EntityViewBuilder extends EntityHandlerBase implements EntityHandlerInterf
* entity components.
* @param string $view_mode
* The view mode that should be used to prepare the entity.
* @param string $langcode
* (optional) For which language the entity should be prepared, defaults to
* the current content language.
*/
protected function alterBuild(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode, $langcode = NULL) { }
protected function alterBuild(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode) { }
/**
* {@inheritdoc}

View file

@ -29,11 +29,8 @@ interface EntityViewBuilderInterface {
* configured for the entity components, keyed by bundle name.
* @param string $view_mode
* The view mode in which the entity is being viewed.
* @param string $langcode
* (optional) For which language the entity should be build, defaults to
* the current content language.
*/
public function buildComponents(array &$build, array $entities, array $displays, $view_mode, $langcode = NULL);
public function buildComponents(array &$build, array $entities, array $displays, $view_mode);
/**
* Builds the render array for the provided entity.

View file

@ -146,10 +146,7 @@ class KeyValueEntityStorage extends EntityStorageBase {
* {@inheritdoc}
*/
public function doDelete($entities) {
$entity_ids = array();
foreach ($entities as $entity) {
$entity_ids[] = $entity->id();
}
$entity_ids = array_keys($entities);
$this->keyValueStore->deleteMultiple($entity_ids);
}

View file

@ -2,7 +2,7 @@
/**
* @file
* Contains \Drupal\Core\Entity\Plugin\Derivative\SelectionBase.
* Contains \Drupal\Core\Entity\Plugin\Derivative\DefaultSelectionDeriver.
*/
namespace Drupal\Core\Entity\Plugin\Derivative;
@ -13,15 +13,15 @@ use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides derivative plugins for Entity Reference Selection plugins.
* Provides derivative plugins for the DefaultSelection plugin.
*
* @see \Drupal\Core\Entity\Plugin\EntityReferenceSelection\SelectionBase
* @see \Drupal\Core\Entity\Plugin\EntityReferenceSelection\DefaultSelection
* @see \Drupal\Core\Entity\EntityReferenceSelection\SelectionPluginManager
* @see \Drupal\Core\Entity\Annotation\EntityReferenceSelection
* @see \Drupal\Core\Entity\EntityReferenceSelection\SelectionInterface
* @see plugin_api
*/
class SelectionBase extends DeriverBase implements ContainerDeriverInterface {
class DefaultSelectionDeriver extends DeriverBase implements ContainerDeriverInterface {
/**
* The entity manager
@ -58,7 +58,20 @@ class SelectionBase extends DeriverBase implements ContainerDeriverInterface {
$this->derivatives[$entity_type_id]['entity_types'] = array($entity_type_id);
$this->derivatives[$entity_type_id]['label'] = t('@entity_type selection', array('@entity_type' => $entity_type->getLabel()));
$this->derivatives[$entity_type_id]['base_plugin_label'] = (string) $base_plugin_definition['label'];
// If the entity type doesn't provide a 'label' key in its plugin
// definition, we have to use the alternate PhpSelection class as default
// plugin, which allows filtering the target entities by their label()
// method. The major downside of PhpSelection is that it is more expensive
// performance-wise than SelectionBase because it has to load all the
// target entities in order to perform the filtering process, regardless
// of whether a limit has been passed.
// @see \Drupal\Core\Entity\Plugin\EntityReferenceSelection\PhpSelection
if (!$entity_type->hasKey('label')) {
$this->derivatives[$entity_type_id]['class'] = 'Drupal\Core\Entity\Plugin\EntityReferenceSelection\PhpSelection';
}
}
return parent::getDerivativeDefinitions($base_plugin_definition);
}

View file

@ -9,7 +9,6 @@ namespace Drupal\Core\Entity\Plugin\EntityReferenceSelection;
use Drupal\Core\Database\Query\SelectInterface;
use Drupal\Core\Entity\EntityReferenceSelection\SelectionInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Form\FormStateInterface;
/**
@ -63,11 +62,6 @@ class Broken implements SelectionInterface {
return array();
}
/**
* {@inheritdoc}
*/
public function validateAutocompleteInput($input, &$element, FormStateInterface $form_state, $form, $strict = TRUE) { }
/**
* {@inheritdoc}
*/

View file

@ -0,0 +1,373 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\Plugin\EntityReferenceSelection\DefaultSelection.
*/
namespace Drupal\Core\Entity\Plugin\EntityReferenceSelection;
use Drupal\Component\Utility\Html;
use Drupal\Core\Database\Query\AlterableInterface;
use Drupal\Core\Database\Query\SelectInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Entity\EntityReferenceSelection\SelectionInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Plugin\PluginBase;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Default plugin implementation of the Entity Reference Selection plugin.
*
* Also serves as a base class for specific types of Entity Reference
* Selection plugins.
*
* @see \Drupal\Core\Entity\EntityReferenceSelection\SelectionPluginManager
* @see \Drupal\Core\Entity\Annotation\EntityReferenceSelection
* @see \Drupal\Core\Entity\EntityReferenceSelection\SelectionInterface
* @see \Drupal\Core\Entity\Plugin\Derivative\DefaultSelectionDeriver
* @see plugin_api
*
* @EntityReferenceSelection(
* id = "default",
* label = @Translation("Default"),
* group = "default",
* weight = 0,
* deriver = "Drupal\Core\Entity\Plugin\Derivative\DefaultSelectionDeriver"
* )
*/
class DefaultSelection extends PluginBase implements SelectionInterface, ContainerFactoryPluginInterface {
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* The module handler service.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* Constructs a new SelectionBase object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager service.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler service.
* @param \Drupal\Core\Session\AccountInterface $current_user
* The current user.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityManagerInterface $entity_manager, ModuleHandlerInterface $module_handler, AccountInterface $current_user) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->entityManager = $entity_manager;
$this->moduleHandler = $module_handler;
$this->currentUser = $current_user;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('entity.manager'),
$container->get('module_handler'),
$container->get('current_user')
);
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$entity_type_id = $this->configuration['target_type'];
$selection_handler_settings = $this->configuration['handler_settings'];
$entity_type = $this->entityManager->getDefinition($entity_type_id);
$bundles = $this->entityManager->getBundleInfo($entity_type_id);
// Merge-in default values.
$selection_handler_settings += array(
// For the 'target_bundles' setting, a NULL value is equivalent to "allow
// entities from any bundle to be referenced" and an empty array value is
// equivalent to "no entities from any bundle can be referenced".
'target_bundles' => NULL,
'sort' => array(
'field' => '_none',
),
'auto_create' => FALSE,
);
if ($entity_type->hasKey('bundle')) {
$bundle_options = array();
foreach ($bundles as $bundle_name => $bundle_info) {
$bundle_options[$bundle_name] = $bundle_info['label'];
}
$form['target_bundles'] = array(
'#type' => 'checkboxes',
'#title' => $this->t('Bundles'),
'#options' => $bundle_options,
'#default_value' => (array) $selection_handler_settings['target_bundles'],
'#required' => TRUE,
'#size' => 6,
'#multiple' => TRUE,
'#element_validate' => [[get_class($this), 'elementValidateFilter']],
);
}
else {
$form['target_bundles'] = array(
'#type' => 'value',
'#value' => array(),
);
}
if ($entity_type->isSubclassOf('\Drupal\Core\Entity\FieldableEntityInterface')) {
$fields = array();
foreach (array_keys($bundles) as $bundle) {
$bundle_fields = array_filter($this->entityManager->getFieldDefinitions($entity_type_id, $bundle), function ($field_definition) {
return !$field_definition->isComputed();
});
foreach ($bundle_fields as $field_name => $field_definition) {
/* @var \Drupal\Core\Field\FieldDefinitionInterface $field_definition */
$columns = $field_definition->getFieldStorageDefinition()->getColumns();
// If there is more than one column, display them all, otherwise just
// display the field label.
// @todo: Use property labels instead of the column name.
if (count($columns) > 1) {
foreach ($columns as $column_name => $column_info) {
$fields[$field_name . '.' . $column_name] = $this->t('@label (@column)', array('@label' => $field_definition->getLabel(), '@column' => $column_name));
}
}
else {
$fields[$field_name] = $this->t('@label', array('@label' => $field_definition->getLabel()));
}
}
}
$form['sort']['field'] = array(
'#type' => 'select',
'#title' => $this->t('Sort by'),
'#options' => array(
'_none' => $this->t('- None -'),
) + $fields,
'#ajax' => TRUE,
'#limit_validation_errors' => array(),
'#default_value' => $selection_handler_settings['sort']['field'],
);
$form['sort']['settings'] = array(
'#type' => 'container',
'#attributes' => array('class' => array('entity_reference-settings')),
'#process' => [[EntityReferenceItem::class, 'formProcessMergeParent']],
);
if ($selection_handler_settings['sort']['field'] != '_none') {
// Merge-in default values.
$selection_handler_settings['sort'] += array(
'direction' => 'ASC',
);
$form['sort']['settings']['direction'] = array(
'#type' => 'select',
'#title' => $this->t('Sort direction'),
'#required' => TRUE,
'#options' => array(
'ASC' => $this->t('Ascending'),
'DESC' => $this->t('Descending'),
),
'#default_value' => $selection_handler_settings['sort']['direction'],
);
}
}
return $form;
}
/**
* {@inheritdoc}
*/
public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
// If no checkboxes were checked for 'target_bundles', store NULL ("all
// bundles are referenceable") rather than empty array ("no bundle is
// referenceable" - typically happens when all referenceable bundles have
// been deleted).
if ($form_state->getValue(['settings', 'handler_settings', 'target_bundles']) === []) {
$form_state->setValue(['settings', 'handler_settings', 'target_bundles'], NULL);
}
}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { }
/**
* Form element validation handler; Filters the #value property of an element.
*/
public static function elementValidateFilter(&$element, FormStateInterface $form_state) {
$element['#value'] = array_filter($element['#value']);
$form_state->setValueForElement($element, $element['#value']);
}
/**
* {@inheritdoc}
*/
public function getReferenceableEntities($match = NULL, $match_operator = 'CONTAINS', $limit = 0) {
$target_type = $this->configuration['target_type'];
$query = $this->buildEntityQuery($match, $match_operator);
if ($limit > 0) {
$query->range(0, $limit);
}
$result = $query->execute();
if (empty($result)) {
return array();
}
$options = array();
$entities = entity_load_multiple($target_type, $result);
foreach ($entities as $entity_id => $entity) {
$bundle = $entity->bundle();
$options[$bundle][$entity_id] = Html::escape($entity->label());
}
return $options;
}
/**
* {@inheritdoc}
*/
public function countReferenceableEntities($match = NULL, $match_operator = 'CONTAINS') {
$query = $this->buildEntityQuery($match, $match_operator);
return $query
->count()
->execute();
}
/**
* {@inheritdoc}
*/
public function validateReferenceableEntities(array $ids) {
$result = array();
if ($ids) {
$target_type = $this->configuration['target_type'];
$entity_type = $this->entityManager->getDefinition($target_type);
$query = $this->buildEntityQuery();
$result = $query
->condition($entity_type->getKey('id'), $ids, 'IN')
->execute();
}
return $result;
}
/**
* Builds an EntityQuery to get referenceable entities.
*
* @param string|null $match
* (Optional) Text to match the label against. Defaults to NULL.
* @param string $match_operator
* (Optional) The operation the matching should be done with. Defaults
* to "CONTAINS".
*
* @return \Drupal\Core\Entity\Query\QueryInterface
* The EntityQuery object with the basic conditions and sorting applied to
* it.
*/
protected function buildEntityQuery($match = NULL, $match_operator = 'CONTAINS') {
$target_type = $this->configuration['target_type'];
$handler_settings = $this->configuration['handler_settings'];
$entity_type = $this->entityManager->getDefinition($target_type);
$query = $this->entityManager->getStorage($target_type)->getQuery();
// If 'target_bundles' is NULL, all bundles are referenceable, no further
// conditions are needed.
if (isset($handler_settings['target_bundles']) && is_array($handler_settings['target_bundles'])) {
// If 'target_bundles' is an empty array, no bundle is referenceable,
// force the query to never return anything and bail out early.
if ($handler_settings['target_bundles'] === []) {
$query->condition($entity_type->getKey('id'), NULL, '=');
return $query;
}
else {
$query->condition($entity_type->getKey('bundle'), $handler_settings['target_bundles'], 'IN');
}
}
if (isset($match) && $label_key = $entity_type->getKey('label')) {
$query->condition($label_key, $match, $match_operator);
}
// Add entity-access tag.
$query->addTag($target_type . '_access');
// Add the Selection handler for system_query_entity_reference_alter().
$query->addTag('entity_reference');
$query->addMetaData('entity_reference_selection_handler', $this);
// Add the sort option.
if (!empty($handler_settings['sort'])) {
$sort_settings = $handler_settings['sort'];
if ($sort_settings['field'] != '_none') {
$query->sort($sort_settings['field'], $sort_settings['direction']);
}
}
return $query;
}
/**
* {@inheritdoc}
*/
public function entityQueryAlter(SelectInterface $query) { }
/**
* Helper method: Passes a query to the alteration system again.
*
* This allows Entity Reference to add a tag to an existing query so it can
* ask access control mechanisms to alter it again.
*/
protected function reAlterQuery(AlterableInterface $query, $tag, $base_table) {
// Save the old tags and metadata.
// For some reason, those are public.
$old_tags = $query->alterTags;
$old_metadata = $query->alterMetaData;
$query->alterTags = array($tag => TRUE);
$query->alterMetaData['base_table'] = $base_table;
$this->moduleHandler->alter(array('query', 'query_' . $tag), $query);
// Restore the tags and metadata.
$query->alterTags = $old_tags;
$query->alterMetaData = $old_metadata;
}
}

View file

@ -0,0 +1,132 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\Plugin\EntityReferenceSelection\PhpSelection.
*/
namespace Drupal\Core\Entity\Plugin\EntityReferenceSelection;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\Unicode;
/**
* Defines an alternative to the default Entity Reference Selection plugin.
*
* This selection plugin uses PHP for more advanced cases when the entity query
* cannot filter properly, for example when the target entity type has no
* 'label' key provided in the entity type plugin definition.
*
* @see \Drupal\Core\Entity\Plugin\Derivative\DefaultSelectionDeriver
*/
class PhpSelection extends DefaultSelection {
/**
* {@inheritdoc}
*/
public function getReferenceableEntities($match = NULL, $match_operator = 'CONTAINS', $limit = 0) {
// No input, return everything from the entity query.
if ($match === NULL || $match === '') {
return parent::getReferenceableEntities($match, $match_operator, $limit);
}
// Start with the selection results returned by the entity query. Don't use
// any limit because we have to apply a limit after filtering the items.
$options = parent::getReferenceableEntities($match, $match_operator);
// Always use a case-insensitive, escaped match. Entity labels returned by
// SelectionInterface::getReferenceableEntities() are already escaped, so
// the incoming $match needs to be escaped as well, making the comparison
// possible.
// @see \Drupal\Core\Entity\EntityReferenceSelection\SelectionInterface::getReferenceableEntities()
if (is_string($match)) {
$match = Html::escape(Unicode::strtolower($match));
}
elseif (is_array($match)) {
array_walk($match, function (&$item) {
$item = Html::escape(Unicode::strtolower($item));
});
}
$filtered = [];
$count = 0;
// Filter target entities by the output of their label() method.
foreach ($options as $bundle => &$items) {
foreach ($items as $entity_id => $label) {
if ($this->matchLabel($match, $match_operator, $label)) {
$filtered[$bundle][$entity_id] = $label;
$count++;
if ($limit && $count >= $limit) {
break 2;
}
}
}
}
return $filtered;
}
/**
* {@inheritdoc}
*/
public function countReferenceableEntities($match = NULL, $match_operator = 'CONTAINS') {
$count = 0;
foreach ($this->getReferenceableEntities($match, $match_operator) as &$items) {
$count += count($items);
}
return $count;
}
/**
* Matches an entity label to an input string.
*
* @param mixed $match
* The value to compare. This can be any valid entity query condition value.
* @param string $match_operator
* The comparison operator.
* @param string $label
* The entity label to match against.
*
* @return bool
* TRUE when matches, FALSE otherwise.
*/
protected function matchLabel($match, $match_operator, $label) {
// Always use a case-insensitive value.
$label = Unicode::strtolower($label);
switch ($match_operator) {
case '=':
return $label == $match;
case '>':
return $label > $match;
case '<':
return $label < $match;
case '>=':
return $label >= $match;
case '<=':
return $label <= $match;
case '<>':
return $label != $match;
case 'IN':
return array_search($label, $match) !== FALSE;
case 'NOT IN':
return array_search($label, $match) === FALSE;
case 'STARTS_WITH':
return strpos($label, $match) === 0;
case 'CONTAINS':
return strpos($label, $match) !== FALSE;
case 'ENDS_WITH':
return Unicode::substr($label, -Unicode::strlen($match)) === (string) $match;
case 'IS NOT NULL':
return TRUE;
case 'IS NULL':
return FALSE;
default:
// Invalid match operator.
return FALSE;
}
}
}

View file

@ -7,376 +7,8 @@
namespace Drupal\Core\Entity\Plugin\EntityReferenceSelection;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Database\Query\AlterableInterface;
use Drupal\Core\Database\Query\SelectInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Entity\EntityReferenceSelection\SelectionInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Plugin\PluginBase;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Default plugin implementation of the Entity Reference Selection plugin.
*
* Also serves as a base class for specific types of Entity Reference
* Selection plugins.
*
* @see \Drupal\Core\Entity\EntityReferenceSelection\SelectionPluginManager
* @see \Drupal\Core\Entity\Annotation\EntityReferenceSelection
* @see \Drupal\Core\Entity\EntityReferenceSelection\SelectionInterface
* @see \Drupal\Core\Entity\Plugin\Derivative\SelectionBase
* @see plugin_api
*
* @EntityReferenceSelection(
* id = "default",
* label = @Translation("Default"),
* group = "default",
* weight = 0,
* deriver = "Drupal\Core\Entity\Plugin\Derivative\SelectionBase"
* )
* @deprecated in Drupal 8.0.0, will be removed before Drupal 9.0.0.
* Use \Drupal\Core\Entity\Plugin\EntityReferenceSelection\DefaultSelection
*/
class SelectionBase extends PluginBase implements SelectionInterface, ContainerFactoryPluginInterface {
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* The module handler service.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* Constructs a new SelectionBase object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager service.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler service.
* @param \Drupal\Core\Session\AccountInterface $current_user
* The current user.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityManagerInterface $entity_manager, ModuleHandlerInterface $module_handler, AccountInterface $current_user) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->entityManager = $entity_manager;
$this->moduleHandler = $module_handler;
$this->currentUser = $current_user;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('entity.manager'),
$container->get('module_handler'),
$container->get('current_user')
);
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$entity_type_id = $this->configuration['target_type'];
$selection_handler_settings = $this->configuration['handler_settings'];
$entity_type = $this->entityManager->getDefinition($entity_type_id);
$bundles = $this->entityManager->getBundleInfo($entity_type_id);
// Merge-in default values.
$selection_handler_settings += array(
'target_bundles' => array(),
'sort' => array(
'field' => '_none',
),
'auto_create' => FALSE,
);
if ($entity_type->hasKey('bundle')) {
$bundle_options = array();
foreach ($bundles as $bundle_name => $bundle_info) {
$bundle_options[$bundle_name] = $bundle_info['label'];
}
$form['target_bundles'] = array(
'#type' => 'checkboxes',
'#title' => $this->t('Bundles'),
'#options' => $bundle_options,
'#default_value' => (!empty($selection_handler_settings['target_bundles'])) ? $selection_handler_settings['target_bundles'] : array(),
'#required' => TRUE,
'#size' => 6,
'#multiple' => TRUE,
'#element_validate' => array('_entity_reference_element_validate_filter'),
);
}
else {
$form['target_bundles'] = array(
'#type' => 'value',
'#value' => array(),
);
}
if ($entity_type->isSubclassOf('\Drupal\Core\Entity\FieldableEntityInterface')) {
$fields = array();
foreach (array_keys($bundles) as $bundle) {
$bundle_fields = array_filter($this->entityManager->getFieldDefinitions($entity_type_id, $bundle), function ($field_definition) {
return !$field_definition->isComputed();
});
foreach ($bundle_fields as $field_name => $field_definition) {
/* @var \Drupal\Core\Field\FieldDefinitionInterface $field_definition */
$columns = $field_definition->getFieldStorageDefinition()->getColumns();
// If there is more than one column, display them all, otherwise just
// display the field label.
// @todo: Use property labels instead of the column name.
if (count($columns) > 1) {
foreach ($columns as $column_name => $column_info) {
$fields[$field_name . '.' . $column_name] = $this->t('@label (@column)', array('@label' => $field_definition->getLabel(), '@column' => $column_name));
}
}
else {
$fields[$field_name] = $this->t('@label', array('@label' => $field_definition->getLabel()));
}
}
}
$form['sort']['field'] = array(
'#type' => 'select',
'#title' => $this->t('Sort by'),
'#options' => array(
'_none' => $this->t('- None -'),
) + $fields,
'#ajax' => TRUE,
'#limit_validation_errors' => array(),
'#default_value' => $selection_handler_settings['sort']['field'],
);
$form['sort']['settings'] = array(
'#type' => 'container',
'#attributes' => array('class' => array('entity_reference-settings')),
'#process' => array('_entity_reference_form_process_merge_parent'),
);
if ($selection_handler_settings['sort']['field'] != '_none') {
// Merge-in default values.
$selection_handler_settings['sort'] += array(
'direction' => 'ASC',
);
$form['sort']['settings']['direction'] = array(
'#type' => 'select',
'#title' => $this->t('Sort direction'),
'#required' => TRUE,
'#options' => array(
'ASC' => $this->t('Ascending'),
'DESC' => $this->t('Descending'),
),
'#default_value' => $selection_handler_settings['sort']['direction'],
);
}
}
return $form;
}
/**
* {@inheritdoc}
*/
public function validateConfigurationForm(array &$form, FormStateInterface $form_state) { }
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { }
/**
* {@inheritdoc}
*/
public function getReferenceableEntities($match = NULL, $match_operator = 'CONTAINS', $limit = 0) {
$target_type = $this->configuration['target_type'];
$query = $this->buildEntityQuery($match, $match_operator);
if ($limit > 0) {
$query->range(0, $limit);
}
$result = $query->execute();
if (empty($result)) {
return array();
}
$options = array();
$entities = entity_load_multiple($target_type, $result);
foreach ($entities as $entity_id => $entity) {
$bundle = $entity->bundle();
$options[$bundle][$entity_id] = SafeMarkup::checkPlain($entity->label());
}
return $options;
}
/**
* {@inheritdoc}
*/
public function countReferenceableEntities($match = NULL, $match_operator = 'CONTAINS') {
$query = $this->buildEntityQuery($match, $match_operator);
return $query
->count()
->execute();
}
/**
* {@inheritdoc}
*/
public function validateReferenceableEntities(array $ids) {
$result = array();
if ($ids) {
$target_type = $this->configuration['target_type'];
$entity_type = $this->entityManager->getDefinition($target_type);
$query = $this->buildEntityQuery();
$result = $query
->condition($entity_type->getKey('id'), $ids, 'IN')
->execute();
}
return $result;
}
/**
* {@inheritdoc}
*/
public function validateAutocompleteInput($input, &$element, FormStateInterface $form_state, $form, $strict = TRUE) {
$bundled_entities = $this->getReferenceableEntities($input, '=', 6);
$entities = array();
foreach ($bundled_entities as $entities_list) {
$entities += $entities_list;
}
$params = array(
'%value' => $input,
'@value' => $input,
);
if (empty($entities)) {
if ($strict) {
// Error if there are no entities available for a required field.
$form_state->setError($element, $this->t('There are no entities matching "%value".', $params));
}
}
elseif (count($entities) > 5) {
$params['@id'] = key($entities);
// Error if there are more than 5 matching entities.
$form_state->setError($element, $this->t('Many entities are called %value. Specify the one you want by appending the id in parentheses, like "@value (@id)".', $params));
}
elseif (count($entities) > 1) {
// More helpful error if there are only a few matching entities.
$multiples = array();
foreach ($entities as $id => $name) {
$multiples[] = $name . ' (' . $id . ')';
}
$params['@id'] = $id;
$form_state->setError($element, $this->t('Multiple entities match this reference; "%multiple". Specify the one you want by appending the id in parentheses, like "@value (@id)".', array('%multiple' => implode('", "', $multiples))));
}
else {
// Take the one and only matching entity.
return key($entities);
}
}
/**
* Builds an EntityQuery to get referenceable entities.
*
* @param string|null $match
* (Optional) Text to match the label against. Defaults to NULL.
* @param string $match_operator
* (Optional) The operation the matching should be done with. Defaults
* to "CONTAINS".
*
* @return \Drupal\Core\Entity\Query\QueryInterface
* The EntityQuery object with the basic conditions and sorting applied to
* it.
*/
protected function buildEntityQuery($match = NULL, $match_operator = 'CONTAINS') {
$target_type = $this->configuration['target_type'];
$handler_settings = $this->configuration['handler_settings'];
$entity_type = $this->entityManager->getDefinition($target_type);
$query = $this->entityManager->getStorage($target_type)->getQuery();
if (!empty($handler_settings['target_bundles'])) {
$query->condition($entity_type->getKey('bundle'), $handler_settings['target_bundles'], 'IN');
}
if (isset($match) && $label_key = $entity_type->getKey('label')) {
$query->condition($label_key, $match, $match_operator);
}
// Add entity-access tag.
$query->addTag($target_type . '_access');
// Add the Selection handler for
// entity_reference_query_entity_reference_alter().
$query->addTag('entity_reference');
$query->addMetaData('entity_reference_selection_handler', $this);
// Add the sort option.
if (!empty($handler_settings['sort'])) {
$sort_settings = $handler_settings['sort'];
if ($sort_settings['field'] != '_none') {
$query->sort($sort_settings['field'], $sort_settings['direction']);
}
}
return $query;
}
/**
* {@inheritdoc}
*/
public function entityQueryAlter(SelectInterface $query) { }
/**
* Helper method: Passes a query to the alteration system again.
*
* This allows Entity Reference to add a tag to an existing query so it can
* ask access control mechanisms to alter it again.
*/
protected function reAlterQuery(AlterableInterface $query, $tag, $base_table) {
// Save the old tags and metadata.
// For some reason, those are public.
$old_tags = $query->alterTags;
$old_metadata = $query->alterMetaData;
$query->alterTags = array($tag => TRUE);
$query->alterMetaData['base_table'] = $base_table;
$this->moduleHandler->alter(array('query', 'query_' . $tag), $query);
// Restore the tags and metadata.
$query->alterTags = $old_tags;
$query->alterMetaData = $old_metadata;
}
}
class SelectionBase extends DefaultSelection { }

View file

@ -25,7 +25,7 @@ class EntityChangedConstraintValidator extends ConstraintValidator {
$saved_entity = \Drupal::entityManager()->getStorage($entity->getEntityTypeId())->loadUnchanged($entity->id());
// A change to any other translation must add a violation to the current
// translation because there might be untranslatable shared fields.
if ($saved_entity && $saved_entity->getChangedTimeAcrossTranslations() > $entity->getChangedTime()) {
if ($saved_entity && $saved_entity->getChangedTimeAcrossTranslations() > $entity->getChangedTimeAcrossTranslations()) {
$this->context->addViolation($constraint->message);
}
}

View file

@ -28,4 +28,11 @@ class ValidReferenceConstraint extends Constraint {
*/
public $message = 'The referenced entity (%type: %id) does not exist.';
/**
* Validation message when the target_id is empty.
*
* @var string
*/
public $nullMessage = 'This value should not be null.';
}

View file

@ -19,10 +19,18 @@ class ValidReferenceConstraintValidator extends ConstraintValidator {
* {@inheritdoc}
*/
public function validate($value, Constraint $constraint) {
/* @var \Drupal\Core\Field\FieldItemInterface $value */
/** @var \Drupal\Core\Field\FieldItemInterface $value */
/** @var ValidReferenceConstraint $constraint */
if (!isset($value)) {
return;
}
// We don't use a regular NotNull constraint for the target_id property as
// a NULL value is valid if the entity property contains an unsaved entity.
// @see \Drupal\Core\TypedData\DataReferenceTargetDefinition::getConstraints
if (!$value->isEmpty() && $value->target_id === NULL && !$value->entity->isNew()) {
$this->context->addViolation($constraint->nullMessage);
return;
}
$id = $value->get('target_id')->getValue();
// '0' or NULL are considered valid empty references.
if (empty($id)) {

View file

@ -230,7 +230,7 @@ abstract class QueryBase implements QueryInterface {
public function sort($field, $direction = 'ASC', $langcode = NULL) {
$this->sort[] = array(
'field' => $field,
'direction' => $direction,
'direction' => strtoupper($direction),
'langcode' => $langcode,
);
return $this;

View file

@ -258,8 +258,8 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The update entity type.
*
* @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
* See https://www.drupal.org/node/2274017.
* @internal Only to be used internally by Entity API. Expected to be
* removed by https://www.drupal.org/node/2274017.
*/
public function setEntityType(EntityTypeInterface $entity_type) {
if ($this->entityType->id() == $entity_type->id()) {
@ -1493,40 +1493,6 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
*/
public function onBundleDelete($bundle, $entity_type_id) { }
/**
* {@inheritdoc}
*/
public function onBundleRename($bundle, $bundle_new, $entity_type_id) {
// The method runs before the field definitions are updated, so we use the
// old bundle name.
$field_definitions = $this->entityManager->getFieldDefinitions($this->entityTypeId, $bundle);
// We need to handle deleted fields too. For now, this only makes sense for
// configurable fields, so we use the specific API.
// @todo Use the unified store of deleted field definitions instead in
// https://www.drupal.org/node/2282119
$field_definitions += entity_load_multiple_by_properties('field_config', array('entity_type' => $this->entityTypeId, 'bundle' => $bundle, 'deleted' => TRUE, 'include_deleted' => TRUE));
$table_mapping = $this->getTableMapping();
foreach ($field_definitions as $field_definition) {
$storage_definition = $field_definition->getFieldStorageDefinition();
if ($table_mapping->requiresDedicatedTableStorage($storage_definition)) {
$is_deleted = $this->storageDefinitionIsDeleted($storage_definition);
$table_name = $table_mapping->getDedicatedDataTableName($storage_definition, $is_deleted);
$revision_name = $table_mapping->getDedicatedRevisionTableName($storage_definition, $is_deleted);
$this->database->update($table_name)
->fields(array('bundle' => $bundle_new))
->condition('bundle', $bundle)
->execute();
if ($this->entityType->isRevisionable()) {
$this->database->update($revision_name)
->fields(array('bundle' => $bundle_new))
->condition('bundle', $bundle)
->execute();
}
}
}
}
/**
* {@inheritdoc}
*/

View file

@ -9,6 +9,7 @@ namespace Drupal\Core\Entity\Sql;
use Drupal\Core\Database\Connection;
use Drupal\Core\Database\DatabaseException;
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
use Drupal\Core\Entity\ContentEntityTypeInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\EntityStorageException;
@ -29,6 +30,8 @@ use Drupal\field\FieldStorageConfigInterface;
*/
class SqlContentEntityStorageSchema implements DynamicallyFieldableEntityStorageSchemaInterface {
use DependencySerializationTrait;
/**
* The entity manager.
*
@ -364,9 +367,14 @@ class SqlContentEntityStorageSchema implements DynamicallyFieldableEntityStorage
$this->originalDefinitions = $field_storage_definitions;
$table_mapping = $this->storage->getTableMapping($field_storage_definitions);
foreach ($field_storage_definitions as $field_storage_definition) {
// If we have a field having dedicated storage we need to drop it,
// otherwise we just remove the related schema data.
if ($table_mapping->requiresDedicatedTableStorage($field_storage_definition)) {
$this->deleteDedicatedTableSchema($field_storage_definition);
}
elseif ($table_mapping->allowsSharedTableStorage($field_storage_definition)) {
$this->deleteFieldSchemaData($field_storage_definition);
}
}
$this->originalDefinitions = NULL;

View file

@ -139,8 +139,8 @@ use Drupal\node\Entity\NodeType;
* - Field configuration preSave(): hook_field_storage_config_update_forbid()
* - Node postSave(): hook_node_access_records() and
* hook_node_access_records_alter()
* - Config entities that are acting as entity bundles, in postSave():
* hook_entity_bundle_create() or hook_entity_bundle_rename() as appropriate
* - Config entities that are acting as entity bundles in postSave():
* hook_entity_bundle_create()
* - Comment: hook_comment_publish() and hook_comment_unpublish() as
* appropriate.
*
@ -350,6 +350,7 @@ use Drupal\node\Entity\NodeType;
* 'bundle_entity_type' on the \Drupal\node\Entity\Node class. Also, the
* bundle config entity type annotation must have a 'bundle_of' entry,
* giving the machine name of the entity type it is acting as a bundle for.
* These machine names are considered permanent, they may not be renamed.
* - Additional annotations can be seen on entity class examples such as
* \Drupal\node\Entity\Node (content) and \Drupal\user\Entity\Role
* (configuration). These annotations are documented on
@ -467,7 +468,9 @@ use Drupal\node\Entity\NodeType;
* @endcode
* Then, to build and render the entity:
* @code
* // You can omit the language ID if the default language is being used.
* // You can omit the language ID, by default the current content language will
* // be used. If no translation is available for the current language, fallback
* // rules will be used.
* $build = $view_builder->view($entity, 'view_mode_name', $language->getId());
* // $build is a render array.
* $rendered = drupal_render($build);
@ -501,6 +504,7 @@ use Drupal\node\Entity\NodeType;
*
* @see i18n
* @see entity_crud
* @see \Drupal\Core\Entity\EntityManagerInterface::getTranslationFromContext()
* @}
*/
@ -517,19 +521,17 @@ use Drupal\node\Entity\NodeType;
* @param string $operation
* The operation that is to be performed on $entity.
* @param \Drupal\Core\Session\AccountInterface $account
* The account trying to access the entity.
* @param string $langcode
* The code of the language $entity is accessed in.
* The account trying to access the entity.
*
* @return \Drupal\Core\Access\AccessResultInterface
* The access result. The final result is calculated by using
* \Drupal\Core\Access\AccessResultInterface::orIf() on the result of every
* hook_entity_access() and hook_ENTITY_TYPE_access() implementation, and the
* result of the entity-specific checkAccess() method in the entity access
* control handler. Be careful when writing generalized access checks shared
* between routing and entity checks: routing uses the andIf() operator. So
* returning an isNeutral() does not determine entity access at all but it
* always ends up denying access while routing.
* The access result. The final result is calculated by using
* \Drupal\Core\Access\AccessResultInterface::orIf() on the result of every
* hook_entity_access() and hook_ENTITY_TYPE_access() implementation, and the
* result of the entity-specific checkAccess() method in the entity access
* control handler. Be careful when writing generalized access checks shared
* between routing and entity checks: routing uses the andIf() operator. So
* returning an isNeutral() does not determine entity access at all but it
* always ends up denying access while routing.
*
* @see \Drupal\Core\Entity\EntityAccessControlHandler
* @see hook_entity_create_access()
@ -537,7 +539,7 @@ use Drupal\node\Entity\NodeType;
*
* @ingroup entity_api
*/
function hook_entity_access(\Drupal\Core\Entity\EntityInterface $entity, $operation, \Drupal\Core\Session\AccountInterface $account, $langcode) {
function hook_entity_access(\Drupal\Core\Entity\EntityInterface $entity, $operation, \Drupal\Core\Session\AccountInterface $account) {
// No opinion.
return AccessResult::neutral();
}
@ -550,12 +552,10 @@ function hook_entity_access(\Drupal\Core\Entity\EntityInterface $entity, $operat
* @param string $operation
* The operation that is to be performed on $entity.
* @param \Drupal\Core\Session\AccountInterface $account
* The account trying to access the entity.
* @param string $langcode
* The code of the language $entity is accessed in.
* The account trying to access the entity.
*
* @return \Drupal\Core\Access\AccessResultInterface
* The access result. hook_entity_access() has detailed documentation.
* The access result. hook_entity_access() has detailed documentation.
*
* @see \Drupal\Core\Entity\EntityAccessControlHandler
* @see hook_ENTITY_TYPE_create_access()
@ -563,7 +563,7 @@ function hook_entity_access(\Drupal\Core\Entity\EntityInterface $entity, $operat
*
* @ingroup entity_api
*/
function hook_ENTITY_TYPE_access(\Drupal\Core\Entity\EntityInterface $entity, $operation, \Drupal\Core\Session\AccountInterface $account, $langcode) {
function hook_ENTITY_TYPE_access(\Drupal\Core\Entity\EntityInterface $entity, $operation, \Drupal\Core\Session\AccountInterface $account) {
// No opinion.
return AccessResult::neutral();
}
@ -572,16 +572,16 @@ function hook_ENTITY_TYPE_access(\Drupal\Core\Entity\EntityInterface $entity, $o
* Control entity create access.
*
* @param \Drupal\Core\Session\AccountInterface $account
* The account trying to access the entity.
* The account trying to access the entity.
* @param array $context
* An associative array of additional context values. By default it contains
* language:
* - langcode - the current language code.
* An associative array of additional context values. By default it contains
* language:
* - langcode - the current language code.
* @param string $entity_bundle
* The entity bundle name.
* The entity bundle name.
*
* @return \Drupal\Core\Access\AccessResultInterface
* The access result.
* The access result.
*
* @see \Drupal\Core\Entity\EntityAccessControlHandler
* @see hook_entity_access()
@ -598,16 +598,16 @@ function hook_entity_create_access(\Drupal\Core\Session\AccountInterface $accoun
* Control entity create access for a specific entity type.
*
* @param \Drupal\Core\Session\AccountInterface $account
* The account trying to access the entity.
* The account trying to access the entity.
* @param array $context
* An associative array of additional context values. By default it contains
* language:
* - langcode - the current language code.
* An associative array of additional context values. By default it contains
* language:
* - langcode - the current language code.
* @param string $entity_bundle
* The entity bundle name.
* The entity bundle name.
*
* @return \Drupal\Core\Access\AccessResultInterface
* The access result.
* The access result.
*
* @see \Drupal\Core\Entity\EntityAccessControlHandler
* @see hook_ENTITY_TYPE_access()
@ -740,31 +740,6 @@ function hook_entity_bundle_create($entity_type_id, $bundle) {
\Drupal::service('router.builder')->setRebuildNeeded();
}
/**
* Act on entity_bundle_rename().
*
* This hook is invoked after the operation has been performed.
*
* @param string $entity_type_id
* The entity type to which the bundle is bound.
* @param string $bundle_old
* The previous name of the bundle.
* @param string $bundle_new
* The new name of the bundle.
*
* @see entity_crud
*/
function hook_entity_bundle_rename($entity_type_id, $bundle_old, $bundle_new) {
// Update the settings associated with the bundle in my_module.settings.
$config = \Drupal::config('my_module.settings');
$bundle_settings = $config->get('bundle_settings');
if (isset($bundle_settings[$entity_type_id][$bundle_old])) {
$bundle_settings[$entity_type_id][$bundle_new] = $bundle_settings[$entity_type_id][$bundle_old];
unset($bundle_settings[$entity_type_id][$bundle_old]);
$config->set('bundle_settings', $bundle_settings);
}
}
/**
* Act on entity_bundle_delete().
*
@ -1064,7 +1039,7 @@ function hook_ENTITY_TYPE_translation_insert(\Drupal\Core\Entity\EntityInterface
*
* This hook runs once the entity translation has been deleted from storage.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* @param \Drupal\Core\Entity\EntityInterface $translation
* The original entity object.
*
* @ingroup entity_crud
@ -1084,7 +1059,7 @@ function hook_entity_translation_delete(\Drupal\Core\Entity\EntityInterface $tra
*
* This hook runs once the entity translation has been deleted from storage.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* @param \Drupal\Core\Entity\EntityInterface $translation
* The original entity object.
*
* @ingroup entity_crud
@ -1256,8 +1231,6 @@ function hook_entity_query_alter(\Drupal\Core\Entity\Query\QueryInterface $query
* entity components.
* @param $view_mode
* The view mode the entity is rendered in.
* @param $langcode
* The language code used for rendering.
*
* The module may add elements to $build prior to rendering. The
* structure of $build is a renderable array as expected by
@ -1268,7 +1241,7 @@ function hook_entity_query_alter(\Drupal\Core\Entity\Query\QueryInterface $query
*
* @ingroup entity_crud
*/
function hook_entity_view(array &$build, \Drupal\Core\Entity\EntityInterface $entity, \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display, $view_mode, $langcode) {
function hook_entity_view(array &$build, \Drupal\Core\Entity\EntityInterface $entity, \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display, $view_mode) {
// 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_entity_extra_field_info().
@ -1292,8 +1265,6 @@ function hook_entity_view(array &$build, \Drupal\Core\Entity\EntityInterface $en
* entity components.
* @param $view_mode
* The view mode the entity is rendered in.
* @param $langcode
* The language code used for rendering.
*
* The module may add elements to $build prior to rendering. The
* structure of $build is a renderable array as expected by
@ -1304,7 +1275,7 @@ function hook_entity_view(array &$build, \Drupal\Core\Entity\EntityInterface $en
*
* @ingroup entity_crud
*/
function hook_ENTITY_TYPE_view(array &$build, \Drupal\Core\Entity\EntityInterface $entity, \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display, $view_mode, $langcode) {
function hook_ENTITY_TYPE_view(array &$build, \Drupal\Core\Entity\EntityInterface $entity, \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display, $view_mode) {
// 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_entity_extra_field_info().
@ -1467,8 +1438,6 @@ function hook_entity_view_mode_alter(&$view_mode, Drupal\Core\Entity\EntityInter
* The entity that is being viewed.
* @param string $view_mode
* The view_mode that is to be used to display the entity.
* @param string $langcode
* The code of the language $entity is accessed in.
*
* @see drupal_render()
* @see \Drupal\Core\Entity\EntityViewBuilder
@ -1476,7 +1445,7 @@ function hook_entity_view_mode_alter(&$view_mode, Drupal\Core\Entity\EntityInter
*
* @ingroup entity_crud
*/
function hook_ENTITY_TYPE_build_defaults_alter(array &$build, \Drupal\Core\Entity\EntityInterface $entity, $view_mode, $langcode) {
function hook_ENTITY_TYPE_build_defaults_alter(array &$build, \Drupal\Core\Entity\EntityInterface $entity, $view_mode) {
}
@ -1493,8 +1462,6 @@ function hook_ENTITY_TYPE_build_defaults_alter(array &$build, \Drupal\Core\Entit
* The entity that is being viewed.
* @param string $view_mode
* The view_mode that is to be used to display the entity.
* @param string $langcode
* The code of the language $entity is accessed in.
*
* @see drupal_render()
* @see \Drupal\Core\Entity\EntityViewBuilder
@ -1502,7 +1469,7 @@ function hook_ENTITY_TYPE_build_defaults_alter(array &$build, \Drupal\Core\Entit
*
* @ingroup entity_crud
*/
function hook_entity_build_defaults_alter(array &$build, \Drupal\Core\Entity\EntityInterface $entity, $view_mode, $langcode) {
function hook_entity_build_defaults_alter(array &$build, \Drupal\Core\Entity\EntityInterface $entity, $view_mode) {
}
@ -1679,7 +1646,7 @@ function hook_entity_base_field_info(\Drupal\Core\Entity\EntityTypeInterface $en
* @see hook_entity_bundle_field_info_alter()
*
* @todo WARNING: This hook will be changed in
* https://www.drupal.org/node/2346329.
* https://www.drupal.org/node/2346329.
*/
function hook_entity_base_field_info_alter(&$fields, \Drupal\Core\Entity\EntityTypeInterface $entity_type) {
// Alter the mymodule_text field to use a custom class.
@ -1714,7 +1681,7 @@ function hook_entity_base_field_info_alter(&$fields, \Drupal\Core\Entity\EntityT
* @see \Drupal\Core\Entity\EntityManagerInterface::getFieldDefinitions()
*
* @todo WARNING: This hook will be changed in
* https://www.drupal.org/node/2346347.
* https://www.drupal.org/node/2346347.
*/
function hook_entity_bundle_field_info(\Drupal\Core\Entity\EntityTypeInterface $entity_type, $bundle, array $base_field_definitions) {
// Add a property only to nodes of the 'article' bundle.
@ -1743,7 +1710,7 @@ function hook_entity_bundle_field_info(\Drupal\Core\Entity\EntityTypeInterface $
* @see hook_entity_bundle_field_info()
*
* @todo WARNING: This hook will be changed in
* https://www.drupal.org/node/2346347.
* https://www.drupal.org/node/2346347.
*/
function hook_entity_bundle_field_info_alter(&$fields, \Drupal\Core\Entity\EntityTypeInterface $entity_type, $bundle) {
if ($entity_type->id() == 'node' && $bundle == 'article' && !empty($fields['mymodule_text'])) {