Drupal 8.0.0 beta 12. More info: https://www.drupal.org/node/2514176

This commit is contained in:
Pantheon Automation 2015-08-17 17:00:26 -07:00 committed by Greg Anderson
commit 9921556621
13277 changed files with 1459781 additions and 0 deletions

View file

@ -0,0 +1,45 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\Annotation\ConfigEntityType.
*/
namespace Drupal\Core\Entity\Annotation;
use Drupal\Core\StringTranslation\TranslationWrapper;
/**
* Defines a config entity type annotation object.
*
* Config Entity type plugins use an object-based annotation method, rather than an
* array-type annotation method (as commonly used on other annotation types).
* The annotation properties of entity types are found on
* \Drupal\Core\Entity\ConfigEntityType and are accessed using
* get/set methods defined in \Drupal\Core\Entity\EntityTypeInterface.
*
* @ingroup entity_api
*
* @Annotation
*/
class ConfigEntityType extends EntityType {
/**
* {@inheritdoc}
*/
public $entity_type_class = 'Drupal\Core\Config\Entity\ConfigEntityType';
/**
* {@inheritdoc}
*/
public $group = 'configuration';
/**
* {@inheritdoc}
*/
public function get() {
$this->definition['group_label'] = new TranslationWrapper('Configuration', array(), array('context' => 'Entity type group'));
return parent::get();
}
}

View file

@ -0,0 +1,45 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\Annotation\ContentEntityType.
*/
namespace Drupal\Core\Entity\Annotation;
use Drupal\Core\StringTranslation\TranslationWrapper;
/**
* Defines a content entity type annotation object.
*
* Content Entity type plugins use an object-based annotation method, rather than an
* array-type annotation method (as commonly used on other annotation types).
* The annotation properties of content entity types are found on
* \Drupal\Core\Entity\ContentEntityType and are accessed using
* get/set methods defined in \Drupal\Core\Entity\ContentEntityTypeInterface.
*
* @ingroup entity_api
*
* @Annotation
*/
class ContentEntityType extends EntityType {
/**
* {@inheritdoc}
*/
public $entity_type_class = 'Drupal\Core\Entity\ContentEntityType';
/**
* {@inheritdoc}
*/
public $group = 'content';
/**
* {@inheritdoc}
*/
public function get() {
$this->definition['group_label'] = new TranslationWrapper('Content', array(), array('context' => 'Entity type group'));
return parent::get();
}
}

View file

@ -0,0 +1,79 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\Annotation\EntityReferenceSelection.
*/
namespace Drupal\Core\Entity\Annotation;
use Drupal\Component\Annotation\Plugin;
/**
* Defines an EntityReferenceSelection plugin annotation object.
*
* Plugin Namespace: Plugin\EntityReferenceSelection
*
* For a working example, see
* \Drupal\comment\Plugin\EntityReferenceSelection\CommentSelection
*
* @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
*/
class EntityReferenceSelection extends Plugin {
/**
* The plugin ID.
*
* @var string
*/
public $id;
/**
* The human-readable name of the selection plugin.
*
* @ingroup plugin_translatable
*
* @var \Drupal\Core\Annotation\Translation
*/
public $label;
/**
* The selection plugin group.
*
* This property is used to allow selection plugins to target a specific
* entity type while also inheriting the code of an existing selection plugin.
* For example, if we want to override the NodeSelection from the 'default'
* selection type, we can define the annotation of a new plugin as follows:
* @code
* id = "node_advanced",
* entity_types = {"node"},
* group = "default",
* weight = 5
* @endcode
*
* @var string
*/
public $group;
/**
* An array of entity types that can be referenced by this plugin. Defaults to
* all entity types.
*
* @var array (optional)
*/
public $entity_types = array();
/**
* The weight of the plugin in it's group.
*
* @var int
*/
public $weight;
}

View file

@ -0,0 +1,66 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\Annotation\EntityType.
*/
namespace Drupal\Core\Entity\Annotation;
use Drupal\Component\Annotation\Plugin;
use Drupal\Core\StringTranslation\StringTranslationTrait;
/**
* Defines an Entity type annotation object.
*
* Entity type plugins use an object-based annotation method, rather than an
* array-type annotation method (as commonly used on other annotation types).
* The annotation properties of entity types are found on
* \Drupal\Core\Entity\EntityType and are accessed using get/set methods defined
* in \Drupal\Core\Entity\EntityTypeInterface.
*
* @ingroup entity_api
*
* @Annotation
*/
class EntityType extends Plugin {
use StringTranslationTrait;
/**
* The class used to represent the entity type.
*
* It must implement \Drupal\Core\Entity\EntityTypeInterface.
*
* @var string
*/
public $entity_type_class = 'Drupal\Core\Entity\EntityType';
/**
* The group machine name.
*/
public $group = 'default';
/**
* The group label.
*
* @var \Drupal\Core\Annotation\Translation
*
* @ingroup plugin_translatable
*/
public $group_label = '';
/**
* {@inheritdoc}
*/
public function get() {
$values = $this->definition;
// Use the specified entity type class, and remove it before instantiating.
$class = $values['entity_type_class'];
unset($values['entity_type_class']);
return new $class($values);
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,129 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\ContentEntityConfirmFormBase.
*/
namespace Drupal\Core\Entity;
use Drupal\Core\Form\ConfirmFormHelper;
use Drupal\Core\Form\ConfirmFormInterface;
use Drupal\Core\Form\FormStateInterface;
/**
* Provides a generic base class for an entity-based confirmation form.
*/
abstract class ContentEntityConfirmFormBase extends ContentEntityForm implements ConfirmFormInterface {
/**
* {@inheritdoc}
*/
public function getBaseFormId() {
return $this->entity->getEntityTypeId() . '_confirm_form';
}
/**
* {@inheritdoc}
*/
public function getDescription() {
return $this->t('This action cannot be undone.');
}
/**
* {@inheritdoc}
*/
public function getConfirmText() {
return $this->t('Confirm');
}
/**
* {@inheritdoc}
*/
public function getCancelText() {
return $this->t('Cancel');
}
/**
* {@inheritdoc}
*/
public function getFormName() {
return 'confirm';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form = parent::buildForm($form, $form_state);
$form['#title'] = $this->getQuestion();
$form['#attributes']['class'][] = 'confirmation';
$form['description'] = array('#markup' => $this->getDescription());
$form[$this->getFormName()] = array('#type' => 'hidden', '#value' => 1);
// By default, render the form using theme_confirm_form().
if (!isset($form['#theme'])) {
$form['#theme'] = 'confirm_form';
}
return $form;
}
/**
* {@inheritdoc}
*/
public function form(array $form, FormStateInterface $form_state) {
// Do not attach fields to the confirm form.
return $form;
}
/**
* {@inheritdoc}
*/
protected function actions(array $form, FormStateInterface $form_state) {
return array(
'submit' => array(
'#type' => 'submit',
'#value' => $this->getConfirmText(),
'#validate' => array(
array($this, 'validate'),
),
'#submit' => array(
array($this, 'submitForm'),
),
),
'cancel' => ConfirmFormHelper::buildCancelLink($this, $this->getRequest()),
);
}
/**
* {@inheritdoc}
*
* The save() method is not used in ContentEntityConfirmFormBase. This
* overrides the default implementation that saves the entity.
*
* Confirmation forms should override submitForm() instead for their logic.
*/
public function save(array $form, FormStateInterface $form_state) {}
/**
* {@inheritdoc}
*
* The delete() method is not used in ContentEntityConfirmFormBase. This
* overrides the default implementation that redirects to the delete-form
* confirmation form.
*
* Confirmation forms should override submitForm() instead for their logic.
*/
public function delete(array $form, FormStateInterface $form_state) {}
/**
* {@inheritdoc}
*/
public function validate(array $form, FormStateInterface $form_state) {
// Override the default validation implementation as it is not necessary
// nor possible to validate an entity in a confirmation form.
}
}

View file

@ -0,0 +1,147 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\ContentEntityDeleteForm.
*/
namespace Drupal\Core\Entity;
use Drupal\Core\Form\FormStateInterface;
/**
* Provides a generic base class for a content entity deletion form.
*
* @todo Re-evaluate and streamline the entity deletion form class hierarchy in
* https://www.drupal.org/node/2491057.
*/
class ContentEntityDeleteForm extends ContentEntityConfirmFormBase {
use EntityDeleteFormTrait {
getQuestion as traitGetQuestion;
logDeletionMessage as traitLogDeletionMessage;
getDeletionMessage as traitGetDeletionMessage;
getCancelUrl as traitGetCancelUrl;
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form = parent::buildForm($form, $form_state);
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
$entity = $this->getEntity();
if ($entity->isDefaultTranslation()) {
if (count($entity->getTranslationLanguages()) > 1) {
$languages = [];
foreach ($entity->getTranslationLanguages() as $language) {
$languages[] = $language->getName();
}
$form['deleted_translations'] = array(
'#theme' => 'item_list',
'#title' => $this->t('The following @entity-type translations will be deleted:', [
'@entity-type' => $entity->getEntityType()->getLowercaseLabel()
]),
'#items' => $languages,
);
$form['actions']['submit']['#value'] = $this->t('Delete all translations');
}
}
else {
$form['actions']['submit']['#value'] = $this->t('Delete @language translation', array('@language' => $entity->language()->getName()));
}
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
$entity = $this->getEntity();
// Make sure that deleting a translation does not delete the whole entity.
if (!$entity->isDefaultTranslation()) {
$untranslated_entity = $entity->getUntranslated();
$untranslated_entity->removeTranslation($entity->language()->getId());
$untranslated_entity->save();
$form_state->setRedirectUrl($untranslated_entity->urlInfo('canonical'));
}
else {
$entity->delete();
$form_state->setRedirectUrl($this->getRedirectUrl());
}
drupal_set_message($this->getDeletionMessage());
$this->logDeletionMessage();
}
/**
* {@inheritdoc}
*/
public function getCancelUrl() {
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
$entity = $this->getEntity();
return $entity->isDefaultTranslation() ? $this->traitGetCancelUrl() : $entity->urlInfo('canonical');
}
/**
* {@inheritdoc}
*/
protected function getDeletionMessage() {
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
$entity = $this->getEntity();
if (!$entity->isDefaultTranslation()) {
return $this->t('The @entity-type %label @language translation has been deleted.', [
'@entity-type' => $entity->getEntityType()->getLowercaseLabel(),
'%label' => $entity->label(),
'@language' => $entity->language()->getName(),
]);
}
return $this->traitGetDeletionMessage();
}
/**
* {@inheritdoc}
*/
protected function logDeletionMessage() {
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
$entity = $this->getEntity();
if (!$entity->isDefaultTranslation()) {
$this->logger($entity->getEntityType()->getProvider())->notice('The @entity-type %label @language translation has been deleted.', [
'@entity-type' => $entity->getEntityType()->getLowercaseLabel(),
'%label' => $entity->label(),
'@language' => $entity->language()->getName(),
]);
}
else {
$this->traitLogDeletionMessage();
}
}
/**
* {@inheritdoc}
*/
public function getQuestion() {
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
$entity = $this->getEntity();
if (!$entity->isDefaultTranslation()) {
return $this->t('Are you sure you want to delete the @language translation of the @entity-type %label?', array(
'@language' => $entity->language()->getName(),
'@entity-type' => $this->getEntity()->getEntityType()->getLowercaseLabel(),
'%label' => $this->getEntity()->label(),
));
}
return $this->traitGetQuestion();
}
}

View file

@ -0,0 +1,261 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\ContentEntityForm.
*/
namespace Drupal\Core\Entity;
use Drupal\Core\Entity\Display\EntityFormDisplayInterface;
use Drupal\Core\Entity\Entity\EntityFormDisplay;
use Drupal\Core\Form\FormStateInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Entity form variant for content entity types.
*
* @see \Drupal\Core\ContentEntityBase
*/
class ContentEntityForm extends EntityForm implements ContentEntityFormInterface {
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* Constructs a ContentEntityForm object.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
*/
public function __construct(EntityManagerInterface $entity_manager) {
$this->entityManager = $entity_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity.manager')
);
}
/**
* {@inheritdoc}
*/
public function form(array $form, FormStateInterface $form_state) {
$form = parent::form($form, $form_state);
// Content entity forms do not use the parent's #after_build callback
// because they only need to rebuild the entity in the validation and the
// submit handler because Field API uses its own #after_build callback for
// its widgets.
unset($form['#after_build']);
$this->getFormDisplay($form_state)->buildForm($this->entity, $form, $form_state);
// Allow modules to act before and after form language is updated.
$form['#entity_builders']['update_form_langcode'] = [$this, 'updateFormLangcode'];
return $form;
}
/**
* {@inheritdoc}
*
* Note that extending classes should not override this method to add entity
* validation logic, but define further validation constraints using the
* entity validation API and/or provide a new validation constraint if
* necessary. This is the only way to ensure that the validation logic
* is correctly applied independently of form submissions; e.g., for REST
* requests.
* For more information about entity validation, see
* https://www.drupal.org/node/2015613.
*/
public function validate(array $form, FormStateInterface $form_state) {
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
$entity = $this->buildEntity($form, $form_state);
$violations = $entity->validate();
// Remove violations of inaccessible fields and not edited fields.
$violations
->filterByFieldAccess($this->currentUser())
->filterByFields(array_diff(array_keys($entity->getFieldDefinitions()), $this->getEditedFieldNames($form_state)));
$this->flagViolations($violations, $form, $form_state);
// @todo Remove this.
// Execute legacy global validation handlers.
$form_state->setValidateHandlers([]);
\Drupal::service('form_validator')->executeValidateHandlers($form, $form_state);
return $entity;
}
/**
* Gets the names of all fields edited in the form.
*
* If the entity form customly adds some fields to the form (i.e. without
* using the form display), it needs to add its fields here and override
* flagViolations() for displaying the violations.
*
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*
* @return string[]
* An array of field names.
*/
protected function getEditedFieldNames(FormStateInterface $form_state) {
return array_keys($this->getFormDisplay($form_state)->getComponents());
}
/**
* Flags violations for the current form.
*
* If the entity form customly adds some fields to the form (i.e. without
* using the form display), it needs to add its fields to array returned by
* getEditedFieldNames() and overwrite this method in order to show any
* violations for those fields; e.g.:
* @code
* foreach ($violations->getByField('name') as $violation) {
* $form_state->setErrorByName('name', $violation->getMessage());
* }
* parent::flagViolations($violations, $form, $form_state);
* @endcode
*
* @param \Drupal\Core\Entity\EntityConstraintViolationListInterface $violations
* The violations to flag.
* @param array $form
* A nested array of form elements comprising the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*/
protected function flagViolations(EntityConstraintViolationListInterface $violations, array $form, FormStateInterface $form_state) {
// Flag entity level violations.
foreach ($violations->getEntityViolations() as $violation) {
/** @var \Symfony\Component\Validator\ConstraintViolationInterface $violation */
$form_state->setErrorByName('', $violation->getMessage());
}
// Let the form display flag violations of its fields.
$this->getFormDisplay($form_state)->flagWidgetsErrorsFromViolations($violations, $form, $form_state);
}
/**
* Initializes the form state and the entity before the first form build.
*
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*/
protected function init(FormStateInterface $form_state) {
// Ensure we act on the translation object corresponding to the current form
// language.
$this->initFormLangcodes($form_state);
$langcode = $this->getFormLangcode($form_state);
$this->entity = $this->entity->getTranslation($langcode);
$form_display = EntityFormDisplay::collectRenderDisplay($this->entity, $this->getOperation());
$this->setFormDisplay($form_display, $form_state);
parent::init($form_state);
}
/**
* Initializes form language code values.
*
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*/
protected function initFormLangcodes(FormStateInterface $form_state) {
// Store the entity default language to allow checking whether the form is
// dealing with the original entity or a translation.
if (!$form_state->has('entity_default_langcode')) {
$form_state->set('entity_default_langcode', $this->entity->getUntranslated()->language()->getId());
}
// This value might have been explicitly populated to work with a particular
// entity translation. If not we fall back to the most proper language based
// on contextual information.
if (!$form_state->has('langcode')) {
// Imply a 'view' operation to ensure users edit entities in the same
// language they are displayed. This allows to keep contextual editing
// working also for multilingual entities.
$form_state->set('langcode', $this->entityManager->getTranslationFromContext($this->entity)->language()->getId());
}
}
/**
* {@inheritdoc}
*/
public function getFormLangcode(FormStateInterface $form_state) {
$this->initFormLangcodes($form_state);
return $form_state->get('langcode');
}
/**
* {@inheritdoc}
*/
public function isDefaultFormLangcode(FormStateInterface $form_state) {
$this->initFormLangcodes($form_state);
return $form_state->get('langcode') == $form_state->get('entity_default_langcode');
}
/**
* {@inheritdoc}
*/
protected function copyFormValuesToEntity(EntityInterface $entity, array $form, FormStateInterface $form_state) {
// First, extract values from widgets.
$extracted = $this->getFormDisplay($form_state)->extractFormValues($entity, $form, $form_state);
// Then extract the values of fields that are not rendered through widgets,
// by simply copying from top-level form values. This leaves the fields
// that are not being edited within this form untouched.
foreach ($form_state->getValues() as $name => $values) {
if ($entity->hasField($name) && !isset($extracted[$name])) {
$entity->set($name, $values);
}
}
}
/**
* {@inheritdoc}
*/
public function getFormDisplay(FormStateInterface $form_state) {
return $form_state->get('form_display');
}
/**
* {@inheritdoc}
*/
public function setFormDisplay(EntityFormDisplayInterface $form_display, FormStateInterface $form_state) {
$form_state->set('form_display', $form_display);
return $this;
}
/**
* Updates the form language to reflect any change to the entity language.
*
* There are use cases for modules to act both before and after form language
* being updated, thus the update is performed through an entity builder
* callback, which allows to support both cases.
*
* @param string $entity_type_id
* The entity type identifier.
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity updated with the submitted values.
* @param array $form
* The complete form array.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*
* @see \Drupal\Core\Entity\ContentEntityForm::form()
*/
public function updateFormLangcode($entity_type_id, EntityInterface $entity, array $form, FormStateInterface $form_state) {
// Update the form language as it might have changed.
if ($this->isDefaultFormLangcode($form_state)) {
$langcode = $entity->language()->getId();
$form_state->set('langcode', $langcode);
}
}
}

View file

@ -0,0 +1,64 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\ContentEntityFormInterface.
*/
namespace Drupal\Core\Entity;
use Drupal\Core\Entity\Display\EntityFormDisplayInterface;
use Drupal\Core\Form\FormStateInterface;
/**
* Defines a common interface for content entity form classes.
*/
interface ContentEntityFormInterface extends EntityFormInterface {
/**
* Gets the form display.
*
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*
* @return \Drupal\Core\Entity\Display\EntityFormDisplayInterface.
* The current form display.
*/
public function getFormDisplay(FormStateInterface $form_state);
/**
* Sets the form display.
*
* Sets the form display which will be used for populating form element
* defaults.
*
* @param \Drupal\Core\Entity\Display\EntityFormDisplayInterface $form_display
* The form display that the current form operates with.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*/
public function setFormDisplay(EntityFormDisplayInterface $form_display, FormStateInterface $form_state);
/**
* Gets the code identifying the active form language.
*
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*
* @return string
* The form language code.
*/
public function getFormLangcode(FormStateInterface $form_state);
/**
* Checks whether the current form language matches the entity one.
*
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*
* @return boolean
* Returns TRUE if the entity form language matches the entity one.
*/
public function isDefaultFormLangcode(FormStateInterface $form_state);
}

View file

@ -0,0 +1,61 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\ContentEntityInterface.
*/
namespace Drupal\Core\Entity;
use Drupal\Core\TypedData\TranslatableInterface;
/**
* Defines a common interface for all content entity objects.
*
* Content entities use fields for all their entity properties and are
* translatable and revisionable, while translations and revisions can be
* enabled per entity type. It's best practice to always implement
* ContentEntityInterface for content-like entities that should be stored in
* some database, and enable/disable revisions and translations as desired.
*
* When implementing this interface which extends Traversable, make sure to list
* IteratorAggregate or Iterator before this interface in the implements clause.
*
* @see \Drupal\Core\Entity\ContentEntityBase
*
* @ingroup entity_api
*/
interface ContentEntityInterface extends \Traversable, FieldableEntityInterface, RevisionableInterface, TranslatableInterface {
/**
* Determines if the current translation of the entity has unsaved changes.
*
* If the entity is translatable only translatable fields will be checked for
* changes.
*
* @return bool
* TRUE if the current translation of the entity has changes.
*/
public function hasTranslationChanges();
/**
* Marks the current revision translation as affected.
*
* @param bool|null $affected
* The flag value. A NULL value can be specified to reset the current value
* and make sure a new value will be computed by the system.
*
* @return $this
*/
public function setRevisionTranslationAffected($affected);
/**
* Checks whether the current translation is affected by the current revision.
*
* @return bool
* TRUE if the entity object is affected by the current revision, FALSE
* otherwise.
*/
public function isRevisionTranslationAffected();
}

View file

@ -0,0 +1,148 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\ContentEntityNullStorage.
*/
namespace Drupal\Core\Entity;
use Drupal\Core\Entity\Query\QueryException;
use Drupal\Core\Field\FieldDefinitionInterface;
/**
* Defines a null entity storage.
*
* Used for content entity types that have no storage.
*/
class ContentEntityNullStorage extends ContentEntityStorageBase {
/**
* {@inheritdoc}
*/
public function loadMultiple(array $ids = NULL) {
return array();
}
/**
* {@inheritdoc}
*/
protected function doLoadMultiple(array $ids = NULL) {
}
/**
* {@inheritdoc}
*/
public function load($id) {
return NULL;
}
/**
* {@inheritdoc}
*/
public function loadRevision($revision_id) {
return NULL;
}
/**
* {@inheritdoc}
*/
public function deleteRevision($revision_id) {
}
/**
* {@inheritdoc}
*/
public function loadByProperties(array $values = array()) {
return array();
}
/**
* {@inheritdoc}
*/
public function delete(array $entities) {
}
/**
* {@inheritdoc}
*/
protected function doDelete($entities) {
}
/**
* {@inheritdoc}
*/
public function save(EntityInterface $entity) {
}
/**
* {@inheritdoc}
*/
protected function getQueryServiceName() {
return 'entity.query.null';
}
/**
* {@inheritdoc}
*/
protected function doLoadFieldItems($entities, $age) {
}
/**
* {@inheritdoc}
*/
protected function doSaveFieldItems(EntityInterface $entity, $update) {
}
/**
* {@inheritdoc}
*/
protected function doDeleteFieldItems(EntityInterface $entity) {
}
/**
* {@inheritdoc}
*/
protected function doDeleteFieldItemsRevision(EntityInterface $entity) {
}
/**
* {@inheritdoc}
*/
protected function readFieldItemsToPurge(FieldDefinitionInterface $field_definition, $batch_size) {
return array();
}
/**
* {@inheritdoc}
*/
protected function purgeFieldItems(ContentEntityInterface $entity, FieldDefinitionInterface $field_definition) {
}
/**
* {@inheritdoc}
*/
protected function doSave($id, EntityInterface $entity) {
}
/**
* {@inheritdoc}
*/
protected function has($id, EntityInterface $entity) {
}
/**
* {@inheritdoc}
*/
public function countFieldData($storage_definition, $as_bool = FALSE) {
return $as_bool ? FALSE : 0;
}
/**
* {@inheritdoc}
*/
public function hasData() {
return FALSE;
}
}

View file

@ -0,0 +1,261 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\ContentEntityStorageBase.
*/
namespace Drupal\Core\Entity;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
abstract class ContentEntityStorageBase extends EntityStorageBase implements DynamicallyFieldableEntityStorageInterface {
/**
* The entity bundle key.
*
* @var string|bool
*/
protected $bundleKey = FALSE;
/**
* Constructs a ContentEntityStorageBase object.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type definition.
*/
public function __construct(EntityTypeInterface $entity_type) {
parent::__construct($entity_type);
$this->bundleKey = $this->entityType->getKey('bundle');
}
/**
* {@inheritdoc}
*/
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
return new static(
$entity_type
);
}
/**
* {@inheritdoc}
*/
public function hasData() {
return (bool) $this->getQuery()
->accessCheck(FALSE)
->range(0, 1)
->execute();
}
/**
* {@inheritdoc}
*/
protected function doCreate(array $values) {
// We have to determine the bundle first.
$bundle = FALSE;
if ($this->bundleKey) {
if (!isset($values[$this->bundleKey])) {
throw new EntityStorageException(SafeMarkup::format('Missing bundle for entity type @type', array('@type' => $this->entityTypeId)));
}
$bundle = $values[$this->bundleKey];
}
$entity = new $this->entityClass(array(), $this->entityTypeId, $bundle);
foreach ($entity as $name => $field) {
if (isset($values[$name])) {
$entity->$name = $values[$name];
}
elseif (!array_key_exists($name, $values)) {
$entity->get($name)->applyDefaultValue();
}
unset($values[$name]);
}
// Set any passed values for non-defined fields also.
foreach ($values as $name => $value) {
$entity->$name = $value;
}
return $entity;
}
/**
* {@inheritdoc}
*/
public function onFieldStorageDefinitionCreate(FieldStorageDefinitionInterface $storage_definition) { }
/**
* {@inheritdoc}
*/
public function onFieldStorageDefinitionUpdate(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original) { }
/**
* {@inheritdoc}
*/
public function onFieldStorageDefinitionDelete(FieldStorageDefinitionInterface $storage_definition) { }
/**
* {@inheritdoc}
*/
public function onFieldDefinitionCreate(FieldDefinitionInterface $field_definition) { }
/**
* {@inheritdoc}
*/
public function onFieldDefinitionUpdate(FieldDefinitionInterface $field_definition, FieldDefinitionInterface $original) { }
/**
* {@inheritdoc}
*/
public function onFieldDefinitionDelete(FieldDefinitionInterface $field_definition) { }
/**
* {@inheritdoc}
*/
public function purgeFieldData(FieldDefinitionInterface $field_definition, $batch_size) {
$items_by_entity = $this->readFieldItemsToPurge($field_definition, $batch_size);
foreach ($items_by_entity as $items) {
$items->delete();
$this->purgeFieldItems($items->getEntity(), $field_definition);
}
return count($items_by_entity);
}
/**
* Reads values to be purged for a single field.
*
* This method is called during field data purge, on fields for which
* onFieldDefinitionDelete() has previously run.
*
* @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
* The field definition.
* @param $batch_size
* The maximum number of field data records to purge before returning.
*
* @return \Drupal\Core\Field\FieldItemListInterface[]
* An array of field item lists, keyed by entity revision id.
*/
abstract protected function readFieldItemsToPurge(FieldDefinitionInterface $field_definition, $batch_size);
/**
* Removes field items from storage per entity during purge.
*
* @param ContentEntityInterface $entity
* The entity revision, whose values are being purged.
* @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
* The field whose values are bing purged.
*/
abstract protected function purgeFieldItems(ContentEntityInterface $entity, FieldDefinitionInterface $field_definition);
/**
* {@inheritdoc}
*/
public function finalizePurge(FieldStorageDefinitionInterface $storage_definition) { }
/**
* Checks translation statuses and invoke the related hooks if needed.
*
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
* The entity being saved.
*/
protected function invokeTranslationHooks(ContentEntityInterface $entity) {
$translations = $entity->getTranslationLanguages(FALSE);
$original_translations = $entity->original->getTranslationLanguages(FALSE);
$all_translations = array_keys($translations + $original_translations);
// Notify modules of translation insertion/deletion.
foreach ($all_translations as $langcode) {
if (isset($translations[$langcode]) && !isset($original_translations[$langcode])) {
$this->invokeHook('translation_insert', $entity->getTranslation($langcode));
}
elseif (!isset($translations[$langcode]) && isset($original_translations[$langcode])) {
$this->invokeHook('translation_delete', $entity->getTranslation($langcode));
}
}
}
/**
* {@inheritdoc}
*/
protected function invokeHook($hook, EntityInterface $entity) {
if ($hook == 'presave') {
$this->invokeFieldMethod('preSave', $entity);
}
parent::invokeHook($hook, $entity);
}
/**
* Invokes a method on the Field objects within an entity.
*
* @param string $method
* The method name.
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
* The entity object.
*/
protected function invokeFieldMethod($method, ContentEntityInterface $entity) {
foreach (array_keys($entity->getTranslationLanguages()) as $langcode) {
$translation = $entity->getTranslation($langcode);
foreach ($translation->getFields() as $field) {
$field->$method();
}
}
}
/**
* Checks whether the field values changed compared to the original entity.
*
* @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
* Field definition of field to compare for changes.
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
* Entity to check for field changes.
* @param \Drupal\Core\Entity\ContentEntityInterface $original
* Original entity to compare against.
*
* @return bool
* True if the field value changed from the original entity.
*/
protected function hasFieldValueChanged(FieldDefinitionInterface $field_definition, ContentEntityInterface $entity, ContentEntityInterface $original) {
$field_name = $field_definition->getName();
$langcodes = array_keys($entity->getTranslationLanguages());
if ($langcodes !== array_keys($original->getTranslationLanguages())) {
// If the list of langcodes has changed, we need to save.
return TRUE;
}
foreach ($langcodes as $langcode) {
$items = $entity->getTranslation($langcode)->get($field_name)->filterEmptyItems();
$original_items = $original->getTranslation($langcode)->get($field_name)->filterEmptyItems();
// If the field items are not equal, we need to save.
if (!$items->equals($original_items)) {
return TRUE;
}
}
return FALSE;
}
/**
* Populates the affected flag for all the revision translations.
*
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
* An entity object being saved.
*/
protected function populateAffectedRevisionTranslations(ContentEntityInterface $entity) {
if ($this->entityType->isTranslatable() && $this->entityType->isRevisionable()) {
$languages = $entity->getTranslationLanguages();
foreach ($languages as $langcode => $language) {
$translation = $entity->getTranslation($langcode);
// Avoid populating the value if it was already manually set.
$affected = $translation->isRevisionTranslationAffected();
if (!isset($affected) && $translation->hasTranslationChanges()) {
$translation->setRevisionTranslationAffected(TRUE);
}
}
}
}
}

View file

@ -0,0 +1,33 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\ContentEntityType.
*/
namespace Drupal\Core\Entity;
/**
* Provides an implementation of a content entity type and its metadata.
*/
class ContentEntityType extends EntityType implements ContentEntityTypeInterface {
/**
* {@inheritdoc}
*/
public function __construct($definition) {
parent::__construct($definition);
$this->handlers += array(
'storage' => 'Drupal\Core\Entity\Sql\SqlContentEntityStorage',
'view_builder' => 'Drupal\Core\Entity\EntityViewBuilder',
);
}
/**
* {@inheritdoc}
*/
public function getConfigDependencyKey() {
return 'content';
}
}

View file

@ -0,0 +1,14 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\ContentEntityTypeInterface.
*/
namespace Drupal\Core\Entity;
/**
* Provides an interface for a content entity type and its metadata.
*/
interface ContentEntityTypeInterface extends EntityTypeInterface {
}

View file

@ -0,0 +1,54 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\ContentUninstallValidator.
*/
namespace Drupal\Core\Entity;
use Drupal\Core\Extension\ModuleUninstallValidatorInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslationInterface;
/**
* Validates module uninstall readiness based on existing content entities.
*/
class ContentUninstallValidator implements ModuleUninstallValidatorInterface {
use StringTranslationTrait;
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* Constructs a new ContentUninstallValidator.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
* The string translation service.
*/
public function __construct(EntityManagerInterface $entity_manager, TranslationInterface $string_translation) {
$this->entityManager = $entity_manager;
$this->stringTranslation = $string_translation;
}
/**
* {@inheritdoc}
*/
public function validate($module) {
$entity_types = $this->entityManager->getDefinitions();
$reasons = array();
foreach ($entity_types as $entity_type) {
if ($module == $entity_type->getProvider() && $entity_type instanceof ContentEntityTypeInterface && $this->entityManager->getStorage($entity_type->id())->hasData()) {
$reasons[] = $this->t('There is content for the entity type: @entity_type', array('@entity_type' => $entity_type->getLabel()));
}
}
return $reasons;
}
}

View file

@ -0,0 +1,32 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\Controller\EntityListController.
*/
namespace Drupal\Core\Entity\Controller;
use Drupal\Core\Controller\ControllerBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines a generic controller to list entities.
*/
class EntityListController extends ControllerBase {
/**
* Provides the listing page for any entity type.
*
* @param string $entity_type
* The entity type to render.
*
* @return array
* A render array as expected by drupal_render().
*/
public function listing($entity_type) {
return $this->entityManager()->getListBuilder($entity_type)->render();
}
}

View file

@ -0,0 +1,113 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\Controller\EntityViewController.
*/
namespace Drupal\Core\Entity\Controller;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Render\RendererInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines a generic controller to render a single entity.
*/
class EntityViewController implements ContainerInjectionInterface {
/**
* The entity manager
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* The renderer service.
*
* @var \Drupal\Core\Render\RendererInterface
*/
protected $renderer;
/**
* Creates an EntityViewController object.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
* @param \Drupal\Core\Render\RendererInterface $renderer
* The renderer service.
*/
public function __construct(EntityManagerInterface $entity_manager, RendererInterface $renderer) {
$this->entityManager = $entity_manager;
$this->renderer = $renderer;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity.manager'),
$container->get('renderer')
);
}
/**
* Pre-render callback to build the page title.
*
* @param array $page
* A page render array.
*
* @return array
* The changed page render array.
*/
public function buildTitle(array $page) {
$entity_type = $page['#entity_type'];
$entity = $page['#' . $entity_type];
// If the entity's label is rendered using a field formatter, set the
// rendered title field formatter as the page title instead of the default
// plain text title. This allows attributes set on the field to propagate
// correctly (e.g. RDFa, in-place editing).
if ($entity instanceof FieldableEntityInterface) {
$label_field = $entity->getEntityType()->getKey('label');
if (isset($page[$label_field])) {
$page['#title'] = $this->renderer->render($page[$label_field]);
}
}
return $page;
}
/**
* Provides a page to render a single entity.
*
* @param \Drupal\Core\Entity\EntityInterface $_entity
* The Entity to be rendered. Note this variable is named $_entity rather
* than $entity to prevent collisions with other named placeholders in the
* route.
* @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) {
$page = $this->entityManager
->getViewBuilder($_entity->getEntityTypeId())
->view($_entity, $view_mode, $langcode);
$page['#pre_render'][] = [$this, 'buildTitle'];
$page['#entity_type'] = $_entity->getEntityTypeId();
return $page;
}
}

View file

@ -0,0 +1,77 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\DependencyTrait.
*/
namespace Drupal\Core\Entity;
/**
* Provides a trait for managing an object's dependencies.
*/
trait DependencyTrait {
/**
* The object's dependencies.
*
* @var array
*/
protected $dependencies = array();
/**
* Adds a dependency.
*
* @param string $type
* Type of dependency being added: 'module', 'theme', 'config', 'content'.
* @param string $name
* If $type is 'module' or 'theme', the name of the module or theme. If
* $type is 'config' or 'content', the result of
* EntityInterface::getConfigDependencyName().
*
* @see \Drupal\Core\Entity\EntityInterface::getConfigDependencyName()
*
* @return $this
*/
protected function addDependency($type, $name) {
if (empty($this->dependencies[$type])) {
$this->dependencies[$type] = array($name);
if (count($this->dependencies) > 1) {
// Ensure a consistent order of type keys.
ksort($this->dependencies);
}
}
elseif (!in_array($name, $this->dependencies[$type])) {
$this->dependencies[$type][] = $name;
// Ensure a consistent order of dependency names.
sort($this->dependencies[$type], SORT_FLAG_CASE);
}
return $this;
}
/**
* Adds multiple dependencies.
*
* @param array $dependencies.
* An array of dependencies keyed by the type of dependency. One example:
* @code
* array(
* 'module' => array(
* 'node',
* 'field',
* 'image',
* ),
* );
* @endcode
*
* @see \Drupal\Core\Entity\DependencyTrait::addDependency
*/
protected function addDependencies(array $dependencies) {
foreach ($dependencies as $dependency_type => $list) {
foreach ($list as $name) {
$this->addDependency($dependency_type, $name);
}
}
}
}

View file

@ -0,0 +1,136 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\Display\EntityDisplayInterface.
*/
namespace Drupal\Core\Entity\Display;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
use Drupal\Core\Entity\EntityWithPluginCollectionInterface;
/**
* Provides a common interface for entity displays.
*/
interface EntityDisplayInterface extends ConfigEntityInterface, EntityWithPluginCollectionInterface {
/**
* Creates a duplicate of the entity display object on a different view mode.
*
* The new object necessarily has the same $targetEntityType and $bundle
* properties than the original one.
*
* @param string $view_mode
* The view mode for the new object.
*
* @return static
* A duplicate of this object with the given view mode.
*/
public function createCopy($view_mode);
/**
* Gets the display options for all components.
*
* @return array
* The array of display options, keyed by component name.
*/
public function getComponents();
/**
* Gets the display options set for a component.
*
* @param string $name
* The name of the component.
*
* @return array|null
* The display options for the component, or NULL if the component is not
* displayed.
*/
public function getComponent($name);
/**
* Sets the display options for a component.
*
* @param string $name
* The name of the component.
* @param array $options
* The display options.
*
* @return $this
*/
public function setComponent($name, array $options = array());
/**
* Sets a component to be hidden.
*
* @param string $name
* The name of the component.
*
* @return $this
*/
public function removeComponent($name);
/**
* Gets the highest weight of the components in the display.
*
* @return int|null
* The highest weight of the components in the display, or NULL if the
* display is empty.
*/
public function getHighestWeight();
/**
* Gets the renderer plugin for a field (e.g. widget, formatter).
*
* @param string $field_name
* The field name.
*
* @return \Drupal\Core\Field\PluginSettingsInterface|null
* A widget or formatter plugin or NULL if the field does not exist.
*/
public function getRenderer($field_name);
/**
* Gets the entity type for which this display is used.
*
* @return string
* The entity type id.
*/
public function getTargetEntityTypeId();
/**
* Gets the view or form mode to be displayed.
*
* @return string
* The mode to be displayed.
*/
public function getMode();
/**
* Gets the original view or form mode that was requested.
*
* @return string
* The original mode that was requested.
*/
public function getOriginalMode();
/**
* Gets the bundle to be displayed.
*
* @return string
* The bundle to be displayed.
*/
public function getTargetBundle();
/**
* Sets the bundle to be displayed.
*
* @param string $bundle
* The bundle to be displayed.
*
* @return $this
*/
public function setTargetBundle($bundle);
}

View file

@ -0,0 +1,184 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\Display\EntityFormDisplayInterface.
*/
namespace Drupal\Core\Entity\Display;
use Drupal\Core\Entity\EntityConstraintViolationListInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Form\FormStateInterface;
/**
* Provides a common interface for entity form displays.
*/
interface EntityFormDisplayInterface extends EntityDisplayInterface {
/**
* Adds field widgets to an entity form.
*
* The form elements for the entity's fields are added by reference as direct
* children in the $form parameter. This parameter can be a full form
* structure (most common case for entity edit forms), or a sub-element of a
* larger form.
*
* By default, submitted field values appear at the top-level of
* $form_state->getValues(). A different location within
* $form_state->getValues() can be specified by setting the '#parents'
* property on the incoming $form parameter. Because of name clashes, two
* instances of the same field cannot appear within the same $form element, or
* within the same '#parents' space.
*
* Sample resulting structure in $form:
* @code
* '#parents' => The location of field values in $form_state->getValues(),
* '#entity_type' => The name of the entity type,
* '#bundle' => The name of the bundle,
* // One sub-array per field appearing in the entity, keyed by field name.
* // The structure of the array differs slightly depending on whether the
* // widget is 'single-value' (provides the input for one field value,
* // most common case), and will therefore be repeated as many times as
* // needed, or 'multiple-values' (one single widget allows the input of
* // several values; e.g., checkboxes, select box, etc.).
* 'field_foo' => array(
* '#access' => TRUE if the current user has 'edit' grants for the field,
* FALSE if not.
* 'widget' => array(
* '#field_name' => The name of the field,
* '#title' => The label of the field,
* '#description' => The description text for the field,
* '#required' => Whether or not the field is required,
* '#field_parents' => The 'parents' space for the field in the form,
* equal to the #parents property of the $form parameter received by
* this method,
*
* // For 'multiple-value' widgets, the remaining elements in the
* // sub-array depend on the widget.
*
* // For 'single-value' widgets:
* '#theme' => 'field_multiple_value_form',
* '#cardinality' => The field cardinality,
* '#cardinality_multiple => TRUE if the field can contain multiple
* items, FALSE otherwise.
* // One sub-array per copy of the widget, keyed by delta.
* 0 => array(
* '#title' => The title to be displayed by the widget,
* '#description' => The description text for the field,
* '#required' => Whether the widget should be marked required,
* '#delta' => 0,
* '#weight' => 0,
* '#field_parents' => Same as above,
* // The remaining elements in the sub-array depend on the widget.
* ...
* ),
* 1 => array(
* ...
* ),
* ...
* ),
* ...
* ),
* )
* @endcode
*
* Additionally, some processing data is placed in $form_state, and can be
* accessed by \Drupal\Core\Field\WidgetBaseInterface::getWidgetState() and
* \Drupal\Core\Field\WidgetBaseInterface::setWidgetState().
*
* @param \Drupal\Core\Entity\FieldableEntityInterface $entity
* The entity.
* @param array $form
* The form structure to fill in. This can be a full form structure, or a
* sub-element of a larger form. The #parents property can be set to
* control the location of submitted field values within
* $form_state->getValues(). If not specified, $form['#parents'] is set to
* an empty array, which results in field values located at the top-level of
* $form_state->getValues().
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state.
*/
public function buildForm(FieldableEntityInterface $entity, array &$form, FormStateInterface $form_state);
/**
* Extracts field values from the submitted widget values into the entity.
*
* This accounts for drag-and-drop reordering of field values, and filtering
* of empty values.
*
* @param \Drupal\Core\Entity\FieldableEntityInterface $entity
* The entity.
* @param array $form
* The form structure where field elements are attached to. This might be a
* full form structure, or a sub-element of a larger form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state.
*
* @return array
* An array whose keys and values are the keys of the top-level entries in
* $form_state->getValues() that have been processed. The remaining entries,
* if any, do not correspond to widgets and should be extracted manually by
* the caller if needed.
*/
public function extractFormValues(FieldableEntityInterface $entity, array &$form, FormStateInterface $form_state);
/**
* Validates submitted widget values and sets the corresponding form errors.
*
* This method invokes entity validation and takes care of flagging them on
* the form. This is particularly useful when all elements on the form are
* managed by the form display.
*
* As an alternative, entity validation can be invoked separately such that
* some violations can be flagged manually. In that case
* \Drupal\Core\Entity\Display\EntityFormDisplayInterface::flagViolations()
* must be used for flagging violations related to the form display.
*
* Note that there are two levels of validation for fields in forms: widget
* validation and field validation:
* - Widget validation steps are specific to a given widget's own form
* structure and UI metaphors. They are executed during normal form
* validation, usually through Form API's #element_validate property.
* Errors reported at this level are typically those that prevent the
* extraction of proper field values from the submitted form input.
* - If no form / widget errors were reported for the field, field validation
* steps are performed according to the "constraints" specified by the
* field definition as part of the entity validation. That validation is
* independent of the specific widget being used in a given form, and is
* also performed on REST entity submissions.
*
* @param \Drupal\Core\Entity\FieldableEntityInterface $entity
* The entity.
* @param array $form
* The form structure where field elements are attached to. This might be a
* full form structure, or a sub-element of a larger form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state.
*/
public function validateFormValues(FieldableEntityInterface $entity, array &$form, FormStateInterface $form_state);
/**
* Flags entity validation violations as form errors.
*
* This method processes all violations passed, thus any violations not
* related to fields of the form display must be processed before this method
* is invoked.
*
* The method flags constraint violations related to fields shown on the
* form as form errors on the correct form elements. Possibly pre-existing
* violations of hidden fields (so fields not appearing in the display) are
* ignored. Other, non-field related violations are passed through and set as
* form errors according to the property path of the violations.
*
* @param \Drupal\Core\Entity\EntityConstraintViolationListInterface $violations
* The violations to flag.
* @param array $form
* The form structure where field elements are attached to. This might be a
* full form structure, or a sub-element of a larger form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state.
*/
public function flagWidgetsErrorsFromViolations(EntityConstraintViolationListInterface $violations, array &$form, FormStateInterface $form_state);
}

View file

@ -0,0 +1,54 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\Display\EntityViewDisplayInterface.
*/
namespace Drupal\Core\Entity\Display;
use Drupal\Core\Entity\FieldableEntityInterface;
/**
* Provides a common interface for entity view displays.
*/
interface EntityViewDisplayInterface extends EntityDisplayInterface {
/**
* Builds a renderable array for the components of an entity.
*
* See the buildMultiple() method for details.
*
* @param \Drupal\Core\Entity\FieldableEntityInterface $entity
* The entity being displayed.
*
* @return array
* A renderable array for the entity.
*
* @see \Drupal\Core\Entity\Display\EntityViewDisplayInterface::buildMultiple()
*/
public function build(FieldableEntityInterface $entity);
/**
* Builds a renderable array for the components of a set of entities.
*
* This only includes the components handled by the Display object, but
* excludes 'extra fields', that are typically rendered through specific,
* ad-hoc code in EntityViewBuilderInterface::buildComponents() or in
* hook_entity_view() implementations.
*
* hook_entity_display_build_alter() is invoked on each entity, allowing 3rd
* party code to alter the render array.
*
* @param \Drupal\Core\Entity\FieldableEntityInterface[] $entities
* The entities being displayed.
*
* @return array
* A renderable array for the entities, indexed by the same keys as the
* $entities array parameter.
*
* @see hook_entity_display_build_alter()
*/
public function buildMultiple(array $entities);
}

View file

@ -0,0 +1,57 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\DynamicallyFieldableEntityStorageInterface.
*/
namespace Drupal\Core\Entity;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldDefinitionListenerInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Field\FieldStorageDefinitionListenerInterface;
/**
* A storage that supports entity types with dynamic field definitions.
*
* A storage that implements this interface can react to the entity type's field
* definitions changing, due to modules being installed or uninstalled, or via
* field UI, or via code changes to the entity class.
*
* For example, configurable fields defined and exposed by field.module.
*/
interface DynamicallyFieldableEntityStorageInterface extends FieldableEntityStorageInterface, FieldStorageDefinitionListenerInterface, FieldDefinitionListenerInterface {
/**
* Determines if the storage contains any data.
*
* @return bool
* TRUE if the storage contains data, FALSE if not.
*/
public function hasData();
/**
* Purges a batch of field data.
*
* @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
* The deleted field whose data is being purged.
* @param $batch_size
* The maximum number of field data records to purge before returning,
* relating to the count of field data records returned by
* \Drupal\Core\Entity\FieldableEntityStorageInterface::countFieldData().
*
* @return int
* The number of field data records that have been purged.
*/
public function purgeFieldData(FieldDefinitionInterface $field_definition, $batch_size);
/**
* Performs final cleanup after all data of a field has been purged.
*
* @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
* The field being purged.
*/
public function finalizePurge(FieldStorageDefinitionInterface $storage_definition);
}

View file

@ -0,0 +1,292 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\Element\EntityAutocomplete.
*/
namespace Drupal\Core\Entity\Element;
use Drupal\Component\Utility\Crypt;
use Drupal\Component\Utility\Tags;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element\Textfield;
use Drupal\Core\Site\Settings;
use Drupal\user\EntityOwnerInterface;
/**
* Provides an entity autocomplete form element.
*
* The #default_value accepted by this element is either an entity object or an
* array of entity objects.
*
* @FormElement("entity_autocomplete")
*/
class EntityAutocomplete extends Textfield {
/**
* {@inheritdoc}
*/
public function getInfo() {
$info = parent::getInfo();
$class = get_class($this);
// Apply default form element properties.
$info['#target_type'] = NULL;
$info['#selection_handler'] = 'default';
$info['#selection_settings'] = array();
$info['#tags'] = FALSE;
$info['#autocreate'] = NULL;
// This should only be set to FALSE if proper validation by the selection
// handler is performed at another level on the extracted form values.
$info['#validate_reference'] = TRUE;
// IMPORTANT! This should only be set to FALSE if the #default_value
// property is processed at another level (e.g. by a Field API widget) and
// it's value is properly checked for access.
$info['#process_default_value'] = TRUE;
$info['#element_validate'] = array(array($class, 'validateEntityAutocomplete'));
array_unshift($info['#process'], array($class, 'processEntityAutocomplete'));
return $info;
}
/**
* {@inheritdoc}
*/
public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
// Process the #default_value property.
if ($input === FALSE && isset($element['#default_value']) && $element['#process_default_value']) {
if (is_array($element['#default_value']) && $element['#tags'] !== TRUE) {
throw new \InvalidArgumentException('The #default_value property is an array but the form element does not allow multiple values.');
}
elseif (!is_array($element['#default_value'])) {
// Convert the default value into an array for easier processing in
// static::getEntityLabels().
$element['#default_value'] = array($element['#default_value']);
}
if ($element['#default_value'] && !(reset($element['#default_value']) instanceof EntityInterface)) {
throw new \InvalidArgumentException('The #default_value property has to be an entity object or an array of entity objects.');
}
// Extract the labels from the passed-in entity objects, taking access
// checks into account.
return static::getEntityLabels($element['#default_value']);
}
}
/**
* Adds entity autocomplete functionality to a form element.
*
* @param array $element
* The form element to process. Properties used:
* - #target_type: The ID of the target entity type.
* - #selection_handler: The plugin ID of the entity reference selection
* handler.
* - #selection_settings: An array of settings that will be passed to the
* selection handler.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
* @param array $complete_form
* The complete form structure.
*
* @return array
* The form element.
*
* @throws \InvalidArgumentException
* Exception thrown when the #target_type or #autocreate['bundle'] are
* missing.
*/
public static function processEntityAutocomplete(array &$element, FormStateInterface $form_state, array &$complete_form) {
// Nothing to do if there is no target entity type.
if (empty($element['#target_type'])) {
throw new \InvalidArgumentException('Missing required #target_type parameter.');
}
// Provide default values and sanity checks for the #autocreate parameter.
if ($element['#autocreate']) {
if (!isset($element['#autocreate']['bundle'])) {
throw new \InvalidArgumentException("Missing required #autocreate['bundle'] parameter.");
}
// Default the autocreate user ID to the current user.
$element['#autocreate']['uid'] = isset($element['#autocreate']['uid']) ? $element['#autocreate']['uid'] : \Drupal::currentUser()->id();
}
// Store the selection settings in the key/value store and pass a hashed key
// in the route parameters.
$selection_settings = isset($element['#selection_settings']) ? $element['#selection_settings'] : [];
$data = serialize($selection_settings) . $element['#target_type'] . $element['#selection_handler'];
$selection_settings_key = Crypt::hmacBase64($data, Settings::getHashSalt());
$key_value_storage = \Drupal::keyValue('entity_autocomplete');
if (!$key_value_storage->has($selection_settings_key)) {
$key_value_storage->set($selection_settings_key, $selection_settings);
}
$element['#autocomplete_route_name'] = 'system.entity_autocomplete';
$element['#autocomplete_route_parameters'] = array(
'target_type' => $element['#target_type'],
'selection_handler' => $element['#selection_handler'],
'selection_settings_key' => $selection_settings_key,
);
return $element;
}
/**
* Form element validation handler for entity_autocomplete elements.
*/
public static function validateEntityAutocomplete(array &$element, FormStateInterface $form_state, array &$complete_form) {
$value = NULL;
if (!empty($element['#value'])) {
$options = array(
'target_type' => $element['#target_type'],
'handler' => $element['#selection_handler'],
'handler_settings' => $element['#selection_settings'],
);
$handler = \Drupal::service('plugin.manager.entity_reference_selection')->getInstance($options);
$autocreate = (bool) $element['#autocreate'];
$input_values = $element['#tags'] ? Tags::explode($element['#value']) : array($element['#value']);
foreach ($input_values as $input) {
$match = static::extractEntityIdFromAutocompleteInput($input);
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);
}
if ($match !== NULL) {
$value[] = array(
'target_id' => $match,
);
}
elseif ($autocreate) {
// Auto-create item. See an example of how this is handled in
// \Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem::presave().
$value[] = array(
'entity' => static::createNewEntity($element['#target_type'], $element['#autocreate']['bundle'], $input, $element['#autocreate']['uid'])
);
}
}
// Check that the referenced entities are valid, if needed.
if ($element['#validate_reference'] && !$autocreate && !empty($value)) {
$ids = array_reduce($value, function ($return, $item) {
if (isset($item['target_id'])) {
$return[] = $item['target_id'];
}
return $return;
});
if ($ids) {
$valid_ids = $handler->validateReferenceableEntities($ids);
if ($invalid_ids = array_diff($ids, $valid_ids)) {
foreach ($invalid_ids as $invalid_id) {
$form_state->setError($element, t('The referenced entity (%type: %id) does not exist.', array('%type' => $element['#target_type'], '%id' => $invalid_id)));
}
}
}
}
// Use only the last value if the form element does not support multiple
// matches (tags).
if (!$element['#tags'] && !empty($value)) {
$last_value = $value[count($value) - 1];
$value = isset($last_value['target_id']) ? $last_value['target_id'] : $last_value;
}
}
$form_state->setValueForElement($element, $value);
}
/**
* Converts an array of entity objects into a string of entity labels.
*
* This method is also responsible for checking the 'view' access on the
* passed-in entities.
*
* @param \Drupal\Core\Entity\EntityInterface[] $entities
* An array of entity objects.
*
* @return string
* A string of entity labels separated by commas.
*/
public static function getEntityLabels(array $entities) {
$entity_labels = array();
foreach ($entities as $entity) {
$label = ($entity->access('view')) ? $entity->label() : t('- Restricted access -');
// Take into account "autocreated" entities.
if (!$entity->isNew()) {
$label .= ' (' . $entity->id() . ')';
}
// Labels containing commas or quotes must be wrapped in quotes.
$entity_labels[] = Tags::encode($label);
}
return implode(', ', $entity_labels);
}
/**
* Extracts the entity ID from the autocompletion result.
*
* @param string $input
* The input coming from the autocompletion result.
*
* @return mixed|null
* An entity ID or NULL if the input does not contain one.
*/
public static function extractEntityIdFromAutocompleteInput($input) {
$match = NULL;
// Take "label (entity id)', match the ID from parenthesis when it's a
// number.
if (preg_match("/.+\((\d+)\)/", $input, $matches)) {
$match = $matches[1];
}
// Match the ID when it's a string (e.g. for config entity types).
elseif (preg_match("/.+\(([\w.]+)\)/", $input, $matches)) {
$match = $matches[1];
}
return $match;
}
/**
* Creates a new entity from a label entered in the autocomplete input.
*
* @param string $entity_type_id
* The entity type ID.
* @param string $bundle
* The bundle name.
* @param string $label
* The entity label.
* @param int $uid
* The entity owner ID.
*
* @return \Drupal\Core\Entity\EntityInterface
*/
protected static function createNewEntity($entity_type_id, $bundle, $label, $uid) {
$entity_manager = \Drupal::entityManager();
$entity_type = $entity_manager->getDefinition($entity_type_id);
$bundle_key = $entity_type->getKey('bundle');
$label_key = $entity_type->getKey('label');
$entity = $entity_manager->getStorage($entity_type_id)->create(array(
$bundle_key => $bundle,
$label_key => $label,
));
if ($entity instanceof EntityOwnerInterface) {
$entity->setOwnerId($uid);
}
return $entity;
}
}

View file

@ -0,0 +1,150 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\Enhancer\EntityRouteEnhancer.
*/
namespace Drupal\Core\Entity\Enhancer;
use Drupal\Core\Routing\Enhancer\RouteEnhancerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Route;
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
/**
* Enhances an entity form route with the appropriate controller.
*/
class EntityRouteEnhancer implements RouteEnhancerInterface {
/**
* {@inheritdoc}
*/
public function enhance(array $defaults, Request $request) {
if (empty($defaults['_controller'])) {
if (!empty($defaults['_entity_form'])) {
$defaults = $this->enhanceEntityForm($defaults, $request);
}
elseif (!empty($defaults['_entity_list'])) {
$defaults = $this->enhanceEntityList($defaults, $request);
}
elseif (!empty($defaults['_entity_view'])) {
$defaults = $this->enhanceEntityView($defaults, $request);
}
}
return $defaults;
}
/**
* {@inheritdoc}
*/
public function applies(Route $route) {
return !$route->hasDefault('_controller') &&
($route->hasDefault('_entity_form')
|| $route->hasDefault('_entity_list')
|| $route->hasDefault('_entity_view')
);
}
/**
* Update defaults for entity forms.
*
* @param array $defaults
* The defaults to modify.
* @param \Symfony\Component\HttpFoundation\Request $request
* The Request instance.
*
* @return array
* The modified defaults.
*/
protected function enhanceEntityForm(array $defaults, Request $request) {
$defaults['_controller'] = 'controller.entity_form:getContentResult';
return $defaults;
}
/**
* Update defaults for an entity list.
*
* @param array $defaults
* The defaults to modify.
* @param \Symfony\Component\HttpFoundation\Request $request
* The Request instance.
*
* @return array
* The modified defaults.
*/
protected function enhanceEntityList(array $defaults, Request $request) {
$defaults['_controller'] = '\Drupal\Core\Entity\Controller\EntityListController::listing';
$defaults['entity_type'] = $defaults['_entity_list'];
unset($defaults['_entity_list']);
return $defaults;
}
/**
* Update defaults for an entity view.
*
* @param array $defaults
* The defaults to modify.
* @param \Symfony\Component\HttpFoundation\Request $request
* The Request instance.
*
* @throws \RuntimeException
* Thrown when an entity of a type cannot be found in a route.
*
* @return array
* The modified defaults.
*/
protected function enhanceEntityView(array $defaults, Request $request) {
$defaults['_controller'] = '\Drupal\Core\Entity\Controller\EntityViewController::view';
if (strpos($defaults['_entity_view'], '.') !== FALSE) {
// The _entity_view entry is of the form entity_type.view_mode.
list($entity_type, $view_mode) = explode('.', $defaults['_entity_view']);
$defaults['view_mode'] = $view_mode;
}
else {
// Only the entity type is nominated, the view mode will use the
// default.
$entity_type = $defaults['_entity_view'];
}
// Set by reference so that we get the upcast value.
if (!empty($defaults[$entity_type])) {
$defaults['_entity'] = &$defaults[$entity_type];
}
else {
// The entity is not keyed by its entity_type. Attempt to find it
// using a converter.
$route = $defaults[RouteObjectInterface::ROUTE_OBJECT];
if ($route && is_object($route)) {
$options = $route->getOptions();
if (isset($options['parameters'])) {
foreach ($options['parameters'] as $name => $details) {
if (!empty($details['type'])) {
$type = $details['type'];
// Type is of the form entity:{entity_type}.
$parameter_entity_type = substr($type, strlen('entity:'));
if ($entity_type == $parameter_entity_type) {
// We have the matching entity type. Set the '_entity' key
// to point to this named placeholder. The entity in this
// position is the one being rendered.
$defaults['_entity'] = &$defaults[$name];
}
}
}
}
else {
throw new \RuntimeException(sprintf('Failed to find entity of type %s in route named %s', $entity_type, $defaults[RouteObjectInterface::ROUTE_NAME]));
}
}
else {
throw new \RuntimeException(sprintf('Failed to find entity of type %s in route named %s', $entity_type, $defaults[RouteObjectInterface::ROUTE_NAME]));
}
}
unset($defaults['_entity_view']);
return $defaults;
}
}

View file

@ -0,0 +1,611 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\Entity.
*/
namespace Drupal\Core\Entity;
use Drupal\Core\Cache\Cache;
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Config\Entity\Exception\ConfigEntityIdLengthException;
use Drupal\Core\Entity\Exception\UndefinedLinkTemplateException;
use Drupal\Core\Language\Language;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Link;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Url;
/**
* Defines a base entity class.
*/
abstract class Entity implements EntityInterface {
use DependencySerializationTrait {
__sleep as traitSleep;
}
/**
* The entity type.
*
* @var string
*/
protected $entityTypeId;
/**
* Boolean indicating whether the entity should be forced to be new.
*
* @var bool
*/
protected $enforceIsNew;
/**
* A typed data object wrapping this entity.
*
* @var \Drupal\Core\TypedData\ComplexDataInterface
*/
protected $typedData;
/**
* Constructs an Entity object.
*
* @param array $values
* An array of values to set, keyed by property name. If the entity type
* has bundles, the bundle key has to be specified.
* @param string $entity_type
* The type of the entity to create.
*/
public function __construct(array $values, $entity_type) {
$this->entityTypeId = $entity_type;
// Set initial values.
foreach ($values as $key => $value) {
$this->$key = $value;
}
}
/**
* Gets the entity manager.
*
* @return \Drupal\Core\Entity\EntityManagerInterface
*/
protected function entityManager() {
return \Drupal::entityManager();
}
/**
* Gets the language manager.
*
* @return \Drupal\Core\Language\LanguageManagerInterface
*/
protected function languageManager() {
return \Drupal::languageManager();
}
/**
* Gets the UUID generator.
*
* @return \Drupal\Component\Uuid\UuidInterface
*/
protected function uuidGenerator() {
return \Drupal::service('uuid');
}
/**
* {@inheritdoc}
*/
public function id() {
return isset($this->id) ? $this->id : NULL;
}
/**
* {@inheritdoc}
*/
public function uuid() {
return isset($this->uuid) ? $this->uuid : NULL;
}
/**
* {@inheritdoc}
*/
public function isNew() {
return !empty($this->enforceIsNew) || !$this->id();
}
/**
* {@inheritdoc}
*/
public function enforceIsNew($value = TRUE) {
$this->enforceIsNew = $value;
return $this;
}
/**
* {@inheritdoc}
*/
public function getEntityTypeId() {
return $this->entityTypeId;
}
/**
* {@inheritdoc}
*/
public function bundle() {
return $this->entityTypeId;
}
/**
* {@inheritdoc}
*/
public function label() {
$label = NULL;
$entity_type = $this->getEntityType();
if (($label_callback = $entity_type->getLabelCallback()) && is_callable($label_callback)) {
$label = call_user_func($label_callback, $this);
}
elseif (($label_key = $entity_type->getKey('label')) && isset($this->{$label_key})) {
$label = $this->{$label_key};
}
return $label;
}
/**
* {@inheritdoc}
*/
public function urlInfo($rel = 'canonical', array $options = []) {
if ($this->id() === NULL) {
throw new EntityMalformedException(sprintf('The "%s" entity cannot have a URI as it does have an ID', $this->getEntityTypeId()));
}
// The links array might contain URI templates set in annotations.
$link_templates = $this->linkTemplates();
// Links pointing to the current revision point to the actual entity. So
// instead of using the 'revision' link, use the 'canonical' link.
if ($rel === 'revision' && $this instanceof RevisionableInterface && $this->isDefaultRevision()) {
$rel = 'canonical';
}
if (isset($link_templates[$rel])) {
$route_parameters = $this->urlRouteParameters($rel);
$route_name = "entity.{$this->entityTypeId}." . str_replace(array('-', 'drupal:'), array('_', ''), $rel);
$uri = new Url($route_name, $route_parameters);
}
else {
$bundle = $this->bundle();
// A bundle-specific callback takes precedence over the generic one for
// the entity type.
$bundles = $this->entityManager()->getBundleInfo($this->getEntityTypeId());
if (isset($bundles[$bundle]['uri_callback'])) {
$uri_callback = $bundles[$bundle]['uri_callback'];
}
elseif ($entity_uri_callback = $this->getEntityType()->getUriCallback()) {
$uri_callback = $entity_uri_callback;
}
// Invoke the callback to get the URI. If there is no callback, use the
// default URI format.
if (isset($uri_callback) && is_callable($uri_callback)) {
$uri = call_user_func($uri_callback, $this);
}
else {
throw new UndefinedLinkTemplateException(SafeMarkup::format('No link template "@rel" found for the "@entity_type" entity type', array(
'@rel' => $rel,
'@entity_type' => $this->getEntityTypeId(),
)));
}
}
// Pass the entity data through as options, so that alter functions do not
// need to look up this entity again.
$uri
->setOption('entity_type', $this->getEntityTypeId())
->setOption('entity', $this);
// Display links by default based on the current language.
if ($rel !== 'collection') {
$options += ['language' => $this->language()];
}
$uri_options = $uri->getOptions();
$uri_options += $options;
return $uri->setOptions($uri_options);
}
/**
* {@inheritdoc}
*/
public function hasLinkTemplate($rel) {
$link_templates = $this->linkTemplates();
return isset($link_templates[$rel]);
}
/**
* Gets an array link templates.
*
* @return array
* An array of link templates containing paths.
*/
protected function linkTemplates() {
return $this->getEntityType()->getLinkTemplates();
}
/**
* {@inheritdoc}
*/
public function link($text = NULL, $rel = 'canonical', array $options = []) {
if (is_null($text)) {
$text = $this->label();
}
$url = $this->urlInfo($rel);
$options += $url->getOptions();
$url->setOptions($options);
return (new Link($text, $url))->toString();
}
/**
* {@inheritdoc}
*/
public function url($rel = 'canonical', $options = array()) {
// While self::urlInfo() will throw an exception if the entity is new,
// the expected result for a URL is always a string.
if ($this->isNew() || !$this->hasLinkTemplate($rel)) {
return '';
}
$uri = $this->urlInfo($rel);
$options += $uri->getOptions();
$uri->setOptions($options);
return $uri->toString();
}
/**
* Gets an array of placeholders for this entity.
*
* Individual entity classes may override this method to add additional
* placeholders if desired. If so, they should be sure to replicate the
* property caching logic.
*
* @param string $rel
* The link relationship type, for example: canonical or edit-form.
*
* @return array
* An array of URI placeholders.
*/
protected function urlRouteParameters($rel) {
$uri_route_parameters = [];
if ($rel != 'collection') {
// The entity ID is needed as a route parameter.
$uri_route_parameters[$this->getEntityTypeId()] = $this->id();
}
if ($rel === 'revision') {
$uri_route_parameters[$this->getEntityTypeId() . '_revision'] = $this->getRevisionId();
}
return $uri_route_parameters;
}
/**
* {@inheritdoc}
*
* Returns a list of URI relationships supported by this entity.
*
* @return array
* An array of link relationships supported by this entity.
*/
public function uriRelationships() {
return array_keys($this->linkTemplates());
}
/**
* {@inheritdoc}
*/
public function access($operation, AccountInterface $account = NULL, $return_as_object = FALSE) {
if ($operation == 'create') {
return $this->entityManager()
->getAccessControlHandler($this->entityTypeId)
->createAccess($this->bundle(), $account, [], $return_as_object);
}
return $this->entityManager()
->getAccessControlHandler($this->entityTypeId)
->access($this, $operation, LanguageInterface::LANGCODE_DEFAULT, $account, $return_as_object);
}
/**
* {@inheritdoc}
*/
public function language() {
if ($key = $this->getEntityType()->getKey('langcode')) {
$langcode = $this->$key;
$language = $this->languageManager()->getLanguage($langcode);
if ($language) {
return $language;
}
}
// Make sure we return a proper language object.
$langcode = !empty($this->langcode) ? $this->langcode : LanguageInterface::LANGCODE_NOT_SPECIFIED;
$language = new Language(array('id' => $langcode));
return $language;
}
/**
* {@inheritdoc}
*/
public function save() {
return $this->entityManager()->getStorage($this->entityTypeId)->save($this);
}
/**
* {@inheritdoc}
*/
public function delete() {
if (!$this->isNew()) {
$this->entityManager()->getStorage($this->entityTypeId)->delete(array($this->id() => $this));
}
}
/**
* {@inheritdoc}
*/
public function createDuplicate() {
$duplicate = clone $this;
$entity_type = $this->getEntityType();
// Reset the entity ID and indicate that this is a new entity.
$duplicate->{$entity_type->getKey('id')} = NULL;
$duplicate->enforceIsNew();
// Check if the entity type supports UUIDs and generate a new one if so.
if ($entity_type->hasKey('uuid')) {
$duplicate->{$entity_type->getKey('uuid')} = $this->uuidGenerator()->generate();
}
return $duplicate;
}
/**
* {@inheritdoc}
*/
public function getEntityType() {
return $this->entityManager()->getDefinition($this->getEntityTypeId());
}
/**
* {@inheritdoc}
*/
public function preSave(EntityStorageInterface $storage) {
// Check if this is an entity bundle.
if ($this->getEntityType()->getBundleOf()) {
// Throw an exception if the bundle ID is longer than 32 characters.
if (Unicode::strlen($this->id()) > EntityTypeInterface::BUNDLE_MAX_LENGTH) {
throw new ConfigEntityIdLengthException(SafeMarkup::format(
'Attempt to create a bundle with an ID longer than @max characters: @id.', array(
'@max' => EntityTypeInterface::BUNDLE_MAX_LENGTH,
'@id' => $this->id(),
)
));
}
}
}
/**
* {@inheritdoc}
*/
public function postSave(EntityStorageInterface $storage, $update = TRUE) {
$this->invalidateTagsOnSave($update);
}
/**
* {@inheritdoc}
*/
public static function preCreate(EntityStorageInterface $storage, array &$values) {
}
/**
* {@inheritdoc}
*/
public function postCreate(EntityStorageInterface $storage) {
}
/**
* {@inheritdoc}
*/
public static function preDelete(EntityStorageInterface $storage, array $entities) {
}
/**
* {@inheritdoc}
*/
public static function postDelete(EntityStorageInterface $storage, array $entities) {
static::invalidateTagsOnDelete($storage->getEntityType(), $entities);
}
/**
* {@inheritdoc}
*/
public static function postLoad(EntityStorageInterface $storage, array &$entities) {
}
/**
* {@inheritdoc}
*/
public function referencedEntities() {
return array();
}
/**
* {@inheritdoc}
*/
public function getCacheContexts() {
return [];
}
/**
* {@inheritdoc}
*/
public function getCacheTags() {
// @todo Add bundle-specific listing cache tag?
// https://www.drupal.org/node/2145751
return [$this->entityTypeId . ':' . $this->id()];
}
/**
* {@inheritdoc}
*/
public function getCacheMaxAge() {
return Cache::PERMANENT;
}
/**
* {@inheritdoc}
*
* @return static|null
* The entity object or NULL if there is no entity with the given ID.
*/
public static function load($id) {
$entity_manager = \Drupal::entityManager();
return $entity_manager->getStorage($entity_manager->getEntityTypeFromClass(get_called_class()))->load($id);
}
/**
* {@inheritdoc}
*
* @return static[]
* An array of entity objects indexed by their IDs. Returns an empty array
* if no matching entities are found.
*/
public static function loadMultiple(array $ids = NULL) {
$entity_manager = \Drupal::entityManager();
return $entity_manager->getStorage($entity_manager->getEntityTypeFromClass(get_called_class()))->loadMultiple($ids);
}
/**
* {@inheritdoc}
*
* @return static
* The entity object.
*/
public static function create(array $values = array()) {
$entity_manager = \Drupal::entityManager();
return $entity_manager->getStorage($entity_manager->getEntityTypeFromClass(get_called_class()))->create($values);
}
/**
* Invalidates an entity's cache tags upon save.
*
* @param bool $update
* TRUE if the entity has been updated, or FALSE if it has been inserted.
*/
protected function invalidateTagsOnSave($update) {
// An entity was created or updated: invalidate its list cache tags. (An
// updated entity may start to appear in a listing because it now meets that
// listing's filtering requirements. A newly created entity may start to
// appear in listings because it did not exist before.)
$tags = $this->getEntityType()->getListCacheTags();
if ($this->hasLinkTemplate('canonical')) {
// Creating or updating an entity may change a cached 403 or 404 response.
$tags = Cache::mergeTags($tags, ['4xx-response']);
}
if ($update) {
// An existing entity was updated, also invalidate its unique cache tag.
$tags = Cache::mergeTags($tags, $this->getCacheTags());
}
Cache::invalidateTags($tags);
}
/**
* Invalidates an entity's cache tags upon delete.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type definition.
* @param \Drupal\Core\Entity\EntityInterface[] $entities
* An array of entities.
*/
protected static function invalidateTagsOnDelete(EntityTypeInterface $entity_type, array $entities) {
$tags = $entity_type->getListCacheTags();
foreach ($entities as $entity) {
// An entity was deleted: invalidate its own cache tag, but also its list
// cache tags. (A deleted entity may cause changes in a paged list on
// other pages than the one it's on. The one it's on is handled by its own
// cache tag, but subsequent list pages would not be invalidated, hence we
// must invalidate its list cache tags as well.)
$tags = Cache::mergeTags($tags, $entity->getCacheTags());
}
Cache::invalidateTags($tags);
}
/**
* {@inheritdoc}
*/
public function getOriginalId() {
// By default, entities do not support renames and do not have original IDs.
return NULL;
}
/**
* {@inheritdoc}
*/
public function setOriginalId($id) {
// By default, entities do not support renames and do not have original IDs.
// If the specified ID is anything except NULL, this should mark this entity
// as no longer new.
if ($id !== NULL) {
$this->enforceIsNew(FALSE);
}
return $this;
}
/**
* {@inheritdoc}
*/
public function toArray() {
return array();
}
/**
* {@inheritdoc}
*/
public function getTypedData() {
if (!isset($this->typedData)) {
$class = \Drupal::typedDataManager()->getDefinition('entity')['class'];
$this->typedData = $class::createFromEntity($this);
}
return $this->typedData;
}
/**
* {@inheritdoc}
*/
public function __sleep() {
$this->typedData = NULL;
return $this->traitSleep();
}
/**
* {@inheritdoc}
*/
public function getConfigDependencyKey() {
return $this->getEntityType()->getConfigDependencyKey();
}
/**
* {@inheritdoc}
*/
public function getConfigDependencyName() {
return $this->getEntityTypeId() . ':' . $this->bundle() . ':' . $this->uuid();
}
/**
* {@inheritdoc}
*/
public function getConfigTarget() {
// For content entities, use the UUID for the config target identifier.
// This ensures that references to the target can be deployed reliably.
return $this->uuid();
}
}

View file

@ -0,0 +1,339 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\Entity\EntityFormDisplay.
*/
namespace Drupal\Core\Entity\Entity;
use Drupal\Core\Entity\EntityConstraintViolationListInterface;
use Drupal\Core\Entity\EntityDisplayPluginCollection;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Entity\Display\EntityFormDisplayInterface;
use Drupal\Core\Entity\EntityDisplayBase;
use Drupal\Core\Form\FormStateInterface;
use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Component\Validator\ConstraintViolationList;
use Symfony\Component\Validator\ConstraintViolationListInterface;
/**
* Configuration entity that contains widget options for all components of a
* entity form in a given form mode.
*
* @ConfigEntityType(
* id = "entity_form_display",
* label = @Translation("Entity form display"),
* entity_keys = {
* "id" = "id",
* "status" = "status"
* },
* config_export = {
* "id",
* "targetEntityType",
* "bundle",
* "mode",
* "content",
* "hidden",
* }
* )
*/
class EntityFormDisplay extends EntityDisplayBase implements EntityFormDisplayInterface {
/**
* {@inheritdoc}
*/
protected $displayContext = 'form';
/**
* Returns the entity_form_display object used to build an entity form.
*
* Depending on the configuration of the form mode for the entity bundle, this
* can be either the display object associated to the form mode, or the
* 'default' display.
*
* This method should only be used internally when rendering an entity form.
* When assigning suggested display options for a component in a given form
* mode, entity_get_form_display() should be used instead, in order to avoid
* inadvertently modifying the output of other form modes that might happen to
* use the 'default' display too. Those options will then be effectively
* applied only if the form mode is configured to use them.
*
* hook_entity_form_display_alter() is invoked on each display, allowing 3rd
* party code to alter the display options held in the display before they are
* used to generate render arrays.
*
* @param \Drupal\Core\Entity\FieldableEntityInterface $entity
* The entity for which the form is being built.
* @param string $form_mode
* The form mode.
*
* @return \Drupal\Core\Entity\Display\EntityFormDisplayInterface
* The display object that should be used to build the entity form.
*
* @see entity_get_form_display()
* @see hook_entity_form_display_alter()
*/
public static function collectRenderDisplay(FieldableEntityInterface $entity, $form_mode) {
$entity_type = $entity->getEntityTypeId();
$bundle = $entity->bundle();
// Check the existence and status of:
// - the display for the form mode,
// - the 'default' display.
if ($form_mode != 'default') {
$candidate_ids[] = $entity_type . '.' . $bundle . '.' . $form_mode;
}
$candidate_ids[] = $entity_type . '.' . $bundle . '.default';
$results = \Drupal::entityQuery('entity_form_display')
->condition('id', $candidate_ids)
->condition('status', TRUE)
->execute();
// Load the first valid candidate display, if any.
$storage = \Drupal::entityManager()->getStorage('entity_form_display');
foreach ($candidate_ids as $candidate_id) {
if (isset($results[$candidate_id])) {
$display = $storage->load($candidate_id);
break;
}
}
// Else create a fresh runtime object.
if (empty($display)) {
$display = $storage->create(array(
'targetEntityType' => $entity_type,
'bundle' => $bundle,
'mode' => $form_mode,
'status' => TRUE,
));
}
// Let the display know which form mode was originally requested.
$display->originalMode = $form_mode;
// Let modules alter the display.
$display_context = array(
'entity_type' => $entity_type,
'bundle' => $bundle,
'form_mode' => $form_mode,
);
\Drupal::moduleHandler()->alter('entity_form_display', $display, $display_context);
return $display;
}
/**
* {@inheritdoc}
*/
public function __construct(array $values, $entity_type) {
$this->pluginManager = \Drupal::service('plugin.manager.field.widget');
parent::__construct($values, $entity_type);
}
/**
* {@inheritdoc}
*/
public function getRenderer($field_name) {
if (isset($this->plugins[$field_name])) {
return $this->plugins[$field_name];
}
// Instantiate the widget object from the stored display properties.
if (($configuration = $this->getComponent($field_name)) && isset($configuration['type']) && ($definition = $this->getFieldDefinition($field_name))) {
$widget = $this->pluginManager->getInstance(array(
'field_definition' => $definition,
'form_mode' => $this->originalMode,
// No need to prepare, defaults have been merged in setComponent().
'prepare' => FALSE,
'configuration' => $configuration
));
}
else {
$widget = NULL;
}
// Persist the widget object.
$this->plugins[$field_name] = $widget;
return $widget;
}
/**
* {@inheritdoc}
*/
public function buildForm(FieldableEntityInterface $entity, array &$form, FormStateInterface $form_state) {
// Set #parents to 'top-level' by default.
$form += array('#parents' => array());
// Let each widget generate the form elements.
foreach ($this->getComponents() as $name => $options) {
if ($widget = $this->getRenderer($name)) {
$items = $entity->get($name);
$items->filterEmptyItems();
$form[$name] = $widget->form($items, $form, $form_state);
$form[$name]['#access'] = $items->access('edit');
// Assign the correct weight. This duplicates the reordering done in
// processForm(), but is needed for other forms calling this method
// directly.
$form[$name]['#weight'] = $options['weight'];
// Associate the cache tags for the field definition & field storage
// definition.
$field_definition = $this->getFieldDefinition($name);
$this->renderer->addCacheableDependency($form[$name], $field_definition);
$this->renderer->addCacheableDependency($form[$name], $field_definition->getFieldStorageDefinition());
}
}
// Associate the cache tags for the form display.
$this->renderer->addCacheableDependency($form, $this);
// Add a process callback so we can assign weights and hide extra fields.
$form['#process'][] = array($this, 'processForm');
}
/**
* Process callback: assigns weights and hides extra fields.
*
* @see \Drupal\Core\Entity\Entity\EntityFormDisplay::buildForm()
*/
public function processForm($element, FormStateInterface $form_state, $form) {
// Assign the weights configured in the form display.
foreach ($this->getComponents() as $name => $options) {
if (isset($element[$name])) {
$element[$name]['#weight'] = $options['weight'];
}
}
// Hide extra fields.
$extra_fields = \Drupal::entityManager()->getExtraFields($this->targetEntityType, $this->bundle);
$extra_fields = isset($extra_fields['form']) ? $extra_fields['form'] : array();
foreach ($extra_fields as $extra_field => $info) {
if (!$this->getComponent($extra_field)) {
$element[$extra_field]['#access'] = FALSE;
}
}
return $element;
}
/**
* {@inheritdoc}
*/
public function extractFormValues(FieldableEntityInterface $entity, array &$form, FormStateInterface $form_state) {
$extracted = array();
foreach ($entity as $name => $items) {
if ($widget = $this->getRenderer($name)) {
$widget->extractFormValues($items, $form, $form_state);
$extracted[$name] = $name;
}
}
return $extracted;
}
/**
* {@inheritdoc}
*/
public function validateFormValues(FieldableEntityInterface $entity, array &$form, FormStateInterface $form_state) {
$violations = $entity->validate();
$violations->filterByFieldAccess();
// Flag entity level violations.
foreach ($violations->getEntityViolations() as $violation) {
/** @var \Symfony\Component\Validator\ConstraintViolationInterface $violation */
$form_state->setErrorByName('', $violation->getMessage());
}
$this->flagWidgetsErrorsFromViolations($violations, $form, $form_state);
}
/**
* {@inheritdoc}
*/
public function flagWidgetsErrorsFromViolations(EntityConstraintViolationListInterface $violations, array &$form, FormStateInterface $form_state) {
$entity = $violations->getEntity();
foreach ($violations->getFieldNames() as $field_name) {
// Only show violations for fields that actually appear in the form, and
// let the widget assign the violations to the correct form elements.
if ($widget = $this->getRenderer($field_name)) {
$field_violations = $this->movePropertyPathViolationsRelativeToField($field_name, $violations->getByField($field_name));
$widget->flagErrors($entity->get($field_name), $field_violations, $form, $form_state);
}
}
}
/**
* Moves the property path to be relative to field level.
*
* @param string $field_name
* The field name.
* @param \Symfony\Component\Validator\ConstraintViolationListInterface $violations
* The violations.
*
* @return \Symfony\Component\Validator\ConstraintViolationList
* A new constraint violation list with the changed property path.
*/
protected function movePropertyPathViolationsRelativeToField($field_name, ConstraintViolationListInterface $violations) {
$new_violations = new ConstraintViolationList();
foreach ($violations as $violation) {
// All the logic below is necessary to change the property path of the
// violations to be relative to the item list, so like title.0.value gets
// changed to 0.value. Sadly constraints in Symfony don't have setters so
// we have to create new objects.
/** @var \Symfony\Component\Validator\ConstraintViolationInterface $violation */
// Create a new violation object with just a different property path.
$violation_path = $violation->getPropertyPath();
$path_parts = explode('.', $violation_path);
if ($path_parts[0] === $field_name) {
unset($path_parts[0]);
}
$new_path = implode('.', $path_parts);
$constraint = NULL;
$cause = NULL;
$parameters = [];
$plural = NULL;
if ($violation instanceof ConstraintViolation) {
$constraint = $violation->getConstraint();
$cause = $violation->getCause();
$parameters = $violation->getParameters();
$plural = $violation->getPlural();
}
$new_violation = new ConstraintViolation(
$violation->getMessage(),
$violation->getMessageTemplate(),
$parameters,
$violation->getRoot(),
$new_path,
$violation->getInvalidValue(),
$plural,
$violation->getCode(),
$constraint,
$cause
);
$new_violations->add($new_violation);
}
return $new_violations;
}
/**
* {@inheritdoc}
*/
public function getPluginCollections() {
$configurations = array();
foreach ($this->getComponents() as $field_name => $configuration) {
if (!empty($configuration['type']) && ($field_definition = $this->getFieldDefinition($field_name))) {
$configurations[$configuration['type']] = $configuration + array(
'field_definition' => $field_definition,
'form_mode' => $this->mode,
);
}
}
return array(
'widgets' => new EntityDisplayPluginCollection($this->pluginManager, $configurations)
);
}
}

View file

@ -0,0 +1,47 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\Entity\EntityFormMode.
*/
namespace Drupal\Core\Entity\Entity;
use Drupal\Core\Entity\EntityDisplayModeBase;
use Drupal\Core\Entity\EntityFormModeInterface;
/**
* Defines the entity form mode configuration entity class.
*
* Form modes allow entity forms to be displayed differently depending on the
* context. For instance, the user entity form can be displayed with a set of
* fields on the 'profile' page (user edit page) and with a different set of
* fields (or settings) on the user registration page. Modules taking part in
* the display of the entity form (notably the Field API) can adjust their
* behavior depending on the requested form mode. An additional 'default' form
* mode is available for all entity types. For each available form mode,
* administrators can configure whether it should use its own set of field
* display settings, or just replicate the settings of the 'default' form mode,
* thus reducing the amount of form display configurations to keep track of.
*
* @see \Drupal\Core\Entity\EntityManagerInterface::getAllFormModes()
* @see \Drupal\Core\Entity\EntityManagerInterface::getFormModes()
*
* @ConfigEntityType(
* id = "entity_form_mode",
* label = @Translation("Form mode"),
* entity_keys = {
* "id" = "id",
* "label" = "label"
* },
* config_export = {
* "id",
* "label",
* "targetEntityType",
* "cache",
* }
* )
*/
class EntityFormMode extends EntityDisplayModeBase implements EntityFormModeInterface {
}

View file

@ -0,0 +1,296 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\Entity\EntityViewDisplay.
*/
namespace Drupal\Core\Entity\Entity;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\EntityDisplayPluginCollection;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Entity\EntityDisplayBase;
/**
* Configuration entity that contains display options for all components of a
* rendered entity in a given view mode.
*
* @ConfigEntityType(
* id = "entity_view_display",
* label = @Translation("Entity view display"),
* entity_keys = {
* "id" = "id",
* "status" = "status"
* },
* config_export = {
* "id",
* "targetEntityType",
* "bundle",
* "mode",
* "content",
* "hidden",
* }
* )
*/
class EntityViewDisplay extends EntityDisplayBase implements EntityViewDisplayInterface {
/**
* {@inheritdoc}
*/
protected $displayContext = 'view';
/**
* Returns the display objects used to render a set of entities.
*
* Depending on the configuration of the view mode for each bundle, this can
* be either the display object associated to the view mode, or the 'default'
* display.
*
* This method should only be used internally when rendering an entity. When
* assigning suggested display options for a component in a given view mode,
* entity_get_display() should be used instead, in order to avoid
* inadvertently modifying the output of other view modes that might happen to
* use the 'default' display too. Those options will then be effectively
* applied only if the view mode is configured to use them.
*
* hook_entity_view_display_alter() is invoked on each display, allowing 3rd
* party code to alter the display options held in the display before they are
* used to generate render arrays.
*
* @param \Drupal\Core\Entity\FieldableEntityInterface[] $entities
* The entities being rendered. They should all be of the same entity type.
* @param string $view_mode
* The view mode being rendered.
*
* @return \Drupal\Core\Entity\Display\EntityViewDisplayInterface[]
* The display objects to use to render the entities, keyed by entity
* bundle.
*
* @see entity_get_display()
* @see hook_entity_view_display_alter()
*/
public static function collectRenderDisplays($entities, $view_mode) {
if (empty($entities)) {
return array();
}
// Collect entity type and bundles.
$entity_type = current($entities)->getEntityTypeId();
$bundles = array();
foreach ($entities as $entity) {
$bundles[$entity->bundle()] = TRUE;
}
$bundles = array_keys($bundles);
// For each bundle, check the existence and status of:
// - the display for the view mode,
// - the 'default' display.
$candidate_ids = array();
foreach ($bundles as $bundle) {
if ($view_mode != 'default') {
$candidate_ids[$bundle][] = $entity_type . '.' . $bundle . '.' . $view_mode;
}
$candidate_ids[$bundle][] = $entity_type . '.' . $bundle . '.default';
}
$results = \Drupal::entityQuery('entity_view_display')
->condition('id', NestedArray::mergeDeepArray($candidate_ids))
->condition('status', TRUE)
->execute();
// For each bundle, select the first valid candidate display, if any.
$load_ids = array();
foreach ($bundles as $bundle) {
foreach ($candidate_ids[$bundle] as $candidate_id) {
if (isset($results[$candidate_id])) {
$load_ids[$bundle] = $candidate_id;
break;
}
}
}
// Load the selected displays.
$storage = \Drupal::entityManager()->getStorage('entity_view_display');
$displays = $storage->loadMultiple($load_ids);
$displays_by_bundle = array();
foreach ($bundles as $bundle) {
// Use the selected display if any, or create a fresh runtime object.
if (isset($load_ids[$bundle])) {
$display = $displays[$load_ids[$bundle]];
}
else {
$display = $storage->create(array(
'targetEntityType' => $entity_type,
'bundle' => $bundle,
'mode' => $view_mode,
'status' => TRUE,
));
}
// Let the display know which view mode was originally requested.
$display->originalMode = $view_mode;
// Let modules alter the display.
$display_context = array(
'entity_type' => $entity_type,
'bundle' => $bundle,
'view_mode' => $view_mode,
);
\Drupal::moduleHandler()->alter('entity_view_display', $display, $display_context);
$displays_by_bundle[$bundle] = $display;
}
return $displays_by_bundle;
}
/**
* Returns the display object used to render an entity.
*
* See the collectRenderDisplays() method for details.
*
* @param \Drupal\Core\Entity\FieldableEntityInterface $entity
* The entity being rendered.
* @param string $view_mode
* The view mode.
*
* @return \Drupal\Core\Entity\Display\EntityViewDisplayInterface
* The display object that should be used to render the entity.
*
* @see \Drupal\Core\Entity\Entity\EntityViewDisplay::collectRenderDisplays()
*/
public static function collectRenderDisplay(FieldableEntityInterface $entity, $view_mode) {
$displays = static::collectRenderDisplays(array($entity), $view_mode);
return $displays[$entity->bundle()];
}
/**
* {@inheritdoc}
*/
public function __construct(array $values, $entity_type) {
$this->pluginManager = \Drupal::service('plugin.manager.field.formatter');
parent::__construct($values, $entity_type);
}
/**
* {@inheritdoc}
*/
public function postSave(EntityStorageInterface $storage, $update = TRUE) {
// Reset the render cache for the target entity type.
if (\Drupal::entityManager()->hasHandler($this->targetEntityType, 'view_builder')) {
\Drupal::entityManager()->getViewBuilder($this->targetEntityType)->resetCache();
}
}
/**
* {@inheritdoc}
*/
public function getRenderer($field_name) {
if (isset($this->plugins[$field_name])) {
return $this->plugins[$field_name];
}
// Instantiate the formatter object from the stored display properties.
if (($configuration = $this->getComponent($field_name)) && isset($configuration['type']) && ($definition = $this->getFieldDefinition($field_name))) {
$formatter = $this->pluginManager->getInstance(array(
'field_definition' => $definition,
'view_mode' => $this->originalMode,
// No need to prepare, defaults have been merged in setComponent().
'prepare' => FALSE,
'configuration' => $configuration
));
}
else {
$formatter = NULL;
}
// Persist the formatter object.
$this->plugins[$field_name] = $formatter;
return $formatter;
}
/**
* {@inheritdoc}
*/
public function build(FieldableEntityInterface $entity) {
$build = $this->buildMultiple(array($entity));
return $build[0];
}
/**
* {@inheritdoc}
*/
public function buildMultiple(array $entities) {
$build_list = array();
foreach ($entities as $key => $entity) {
$build_list[$key] = array();
}
// Run field formatters.
foreach ($this->getComponents() as $name => $options) {
if ($formatter = $this->getRenderer($name)) {
// Group items across all entities and pass them to the formatter's
// prepareView() method.
$grouped_items = array();
foreach ($entities as $id => $entity) {
$items = $entity->get($name);
$items->filterEmptyItems();
$grouped_items[$id] = $items;
}
$formatter->prepareView($grouped_items);
// Then let the formatter build the output for each entity.
foreach ($entities as $id => $entity) {
$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) : [];
// Apply the field access cacheability metadata to the render array.
$this->renderer->addCacheableDependency($build_list[$id][$name], $field_access);
}
}
}
foreach ($entities as $id => $entity) {
// Assign the configured weights.
foreach ($this->getComponents() as $name => $options) {
if (isset($build_list[$id][$name])) {
$build_list[$id][$name]['#weight'] = $options['weight'];
}
}
// Let other modules alter the renderable array.
$context = array(
'entity' => $entity,
'view_mode' => $this->originalMode,
'display' => $this,
);
\Drupal::moduleHandler()->alter('entity_display_build', $build_list[$key], $context);
}
return $build_list;
}
/**
* {@inheritdoc}
*/
public function getPluginCollections() {
$configurations = array();
foreach ($this->getComponents() as $field_name => $configuration) {
if (!empty($configuration['type']) && ($field_definition = $this->getFieldDefinition($field_name))) {
$configurations[$configuration['type']] = $configuration + array(
'field_definition' => $field_definition,
'view_mode' => $this->originalMode,
);
}
}
return array(
'formatters' => new EntityDisplayPluginCollection($this->pluginManager, $configurations)
);
}
}

View file

@ -0,0 +1,49 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\Entity\EntityViewMode.
*/
namespace Drupal\Core\Entity\Entity;
use Drupal\Core\Entity\EntityDisplayModeBase;
use Drupal\Core\Entity\EntityViewModeInterface;
/**
* Defines the entity view mode configuration entity class.
*
* View modes let entities be displayed differently depending on the context.
* For instance, a node can be displayed differently on its own page ('full'
* mode), on the home page or taxonomy listings ('teaser' mode), or in an RSS
* feed ('rss' mode). Modules taking part in the display of the entity (notably
* the Field API) can adjust their behavior depending on the requested view
* mode. An additional 'default' view mode is available for all entity types.
* This view mode is not intended for actual entity display, but holds default
* display settings. For each available view mode, administrators can configure
* whether it should use its own set of field display settings, or just
* replicate the settings of the 'default' view mode, thus reducing the amount
* of display configurations to keep track of.
*
* @see \Drupal\Core\Entity\EntityManagerInterface::getAllViewModes()
* @see \Drupal\Core\Entity\EntityManagerInterface::getViewModes()
* @see hook_entity_view_mode_info_alter()
*
* @ConfigEntityType(
* id = "entity_view_mode",
* label = @Translation("View mode"),
* entity_keys = {
* "id" = "id",
* "label" = "label"
* },
* config_export = {
* "id",
* "label",
* "targetEntityType",
* "cache",
* }
* )
*/
class EntityViewMode extends EntityDisplayModeBase implements EntityViewModeInterface {
}

View file

@ -0,0 +1,61 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\EntityAccessCheck.
*/
namespace Drupal\Core\Entity;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Routing\Access\AccessInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\Routing\Route;
/**
* Provides a generic access checker for entities.
*/
class EntityAccessCheck implements AccessInterface {
/**
* Checks access to the entity operation on the given route.
*
* The value of the '_entity_access' key must be in the pattern
* 'entity_type.operation.' The entity type must match the {entity_type}
* parameter in the route pattern. This will check a node for 'update' access:
* @code
* pattern: '/foo/{node}/bar'
* requirements:
* _entity_access: 'node.update'
* @endcode
* Available operations are 'view', 'update', 'create', and 'delete'.
*
* @param \Symfony\Component\Routing\Route $route
* The route to check against.
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* The parametrized route
* @param \Drupal\Core\Session\AccountInterface $account
* The currently logged in account.
*
* @return \Drupal\Core\Access\AccessResultInterface
* The access result.
*/
public function access(Route $route, RouteMatchInterface $route_match, AccountInterface $account) {
// Split the entity type and the operation.
$requirement = $route->getRequirement('_entity_access');
list($entity_type, $operation) = explode('.', $requirement);
// If there is valid entity of the given entity type, check its access.
$parameters = $route_match->getParameters();
if ($parameters->has($entity_type)) {
$entity = $parameters->get($entity_type);
if ($entity instanceof EntityInterface) {
return $entity->access($operation, $account, TRUE);
}
}
// No opinion, so other access checks should decide if access should be
// allowed or not.
return AccessResult::neutral();
}
}

View file

@ -0,0 +1,346 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\EntityAccessControlHandler.
*/
namespace Drupal\Core\Entity;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Session\AccountInterface;
/**
* Defines a default implementation for entity access control handler.
*/
class EntityAccessControlHandler extends EntityHandlerBase implements EntityAccessControlHandlerInterface {
/**
* Stores calculated access check results.
*
* @var array
*/
protected $accessCache = array();
/**
* The entity type ID of the access control handler instance.
*
* @var string
*/
protected $entityTypeId;
/**
* Information about the entity type.
*
* @var \Drupal\Core\Entity\EntityTypeInterface
*/
protected $entityType;
/**
* Constructs an access control handler instance.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type definition.
*/
public function __construct(EntityTypeInterface $entity_type) {
$this->entityTypeId = $entity_type->id();
$this->entityType = $entity_type;
}
/**
* {@inheritdoc}
*/
public function access(EntityInterface $entity, $operation, $langcode = LanguageInterface::LANGCODE_DEFAULT, AccountInterface $account = NULL, $return_as_object = FALSE) {
$account = $this->prepareUser($account);
if (($return = $this->getCache($entity->uuid(), $operation, $langcode, $account)) !== NULL) {
// Cache hit, no work necessary.
return $return_as_object ? $return : $return->isAllowed();
}
// Invoke hook_entity_access() and hook_ENTITY_TYPE_access(). Hook results
// take precedence over overridden implementations of
// EntityAccessControlHandler::checkAccess(). Entities that have checks that
// need to be done before the hook is invoked should do so by overriding
// this method.
// We grant access to the entity if both of these conditions are met:
// - 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))
);
$return = $this->processAccessHookResults($access);
// 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));
}
$result = $this->setCache($return, $entity->uuid(), $operation, $langcode, $account);
return $return_as_object ? $result : $result->isAllowed();
}
/**
* We grant access to the entity if both of these conditions are met:
* - No modules say to deny access.
* - At least one module says to grant access.
*
* @param \Drupal\Core\Access\AccessResultInterface[] $access
* An array of access results of the fired access hook.
*
* @return \Drupal\Core\Access\AccessResultInterface
* The combined result of the various access checks' results. All their
* cacheability metadata is merged as well.
*
* @see \Drupal\Core\Access\AccessResultInterface::orIf()
*/
protected function processAccessHookResults(array $access) {
// No results means no opinion.
if (empty($access)) {
return AccessResult::neutral();
}
/** @var \Drupal\Core\Access\AccessResultInterface $result */
$result = array_shift($access);
foreach ($access as $other) {
$result = $result->orIf($other);
}
return $result;
}
/**
* Performs access checks.
*
* This method is supposed to be overwritten by extending classes that
* do their own custom access checking.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* 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) {
if ($operation == 'delete' && $entity->isNew()) {
return AccessResult::forbidden()->cacheUntilEntityChanges($entity);
}
if ($admin_permission = $this->entityType->getAdminPermission()) {
return AccessResult::allowedIfHasPermission($account, $this->entityType->getAdminPermission());
}
else {
// No opinion.
return AccessResult::neutral();
}
}
/**
* Tries to retrieve a previously cached access value from the static cache.
*
* @param string $cid
* Unique string identifier for the entity/operation, for example the
* entity UUID or a custom string.
* @param string $operation
* The entity operation. Usually one of 'view', 'update', 'create' 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|null
* The cached AccessResult, or NULL if there is no record for the given
* user, operation, langcode and entity in the cache.
*/
protected function getCache($cid, $operation, $langcode, AccountInterface $account) {
// Return from cache if a value has been set for it previously.
if (isset($this->accessCache[$account->id()][$cid][$langcode][$operation])) {
return $this->accessCache[$account->id()][$cid][$langcode][$operation];
}
}
/**
* Statically caches whether the given user has access.
*
* @param \Drupal\Core\Access\AccessResultInterface $access
* The access result.
* @param string $cid
* Unique string identifier for the entity/operation, for example the
* entity UUID or a custom string.
* @param string $operation
* The entity operation. Usually one of 'view', 'update', 'create' 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
* Whether the user has access, plus cacheability metadata.
*/
protected function setCache($access, $cid, $operation, $langcode, AccountInterface $account) {
// Save the given value in the static cache and directly return it.
return $this->accessCache[$account->id()][$cid][$langcode][$operation] = $access;
}
/**
* {@inheritdoc}
*/
public function resetCache() {
$this->accessCache = array();
}
/**
* {@inheritdoc}
*/
public function createAccess($entity_bundle = NULL, AccountInterface $account = NULL, array $context = array(), $return_as_object = FALSE) {
$account = $this->prepareUser($account);
$context += array(
'langcode' => LanguageInterface::LANGCODE_DEFAULT,
);
$cid = $entity_bundle ? 'create:' . $entity_bundle : 'create';
if (($access = $this->getCache($cid, 'create', $context['langcode'], $account)) !== NULL) {
// Cache hit, no work necessary.
return $return_as_object ? $access : $access->isAllowed();
}
// Invoke hook_entity_create_access() and hook_ENTITY_TYPE_create_access().
// Hook results take precedence over overridden implementations of
// EntityAccessControlHandler::checkCreateAccess(). Entities that have
// checks that need to be done before the hook is invoked should do so by
// overriding this method.
// We grant access to the entity if both of these conditions are met:
// - No modules say to deny access.
// - At least one module says to grant access.
$access = array_merge(
$this->moduleHandler()->invokeAll('entity_create_access', array($account, $context, $entity_bundle)),
$this->moduleHandler()->invokeAll($this->entityTypeId . '_create_access', array($account, $context, $entity_bundle))
);
$return = $this->processAccessHookResults($access);
// 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->checkCreateAccess($account, $context, $entity_bundle));
}
$result = $this->setCache($return, $cid, 'create', $context['langcode'], $account);
return $return_as_object ? $result : $result->isAllowed();
}
/**
* Performs create access checks.
*
* This method is supposed to be overwritten by extending classes that
* do their own custom access checking.
*
* @param \Drupal\Core\Session\AccountInterface $account
* The user for which to check access.
* @param array $context
* An array of key-value pairs to pass additional context when needed.
* @param string|null $entity_bundle
* (optional) The bundle of the entity. Required if the entity supports
* bundles, defaults to NULL otherwise.
*
* @return \Drupal\Core\Access\AccessResultInterface
* The access result.
*/
protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) {
if ($admin_permission = $this->entityType->getAdminPermission()) {
return AccessResult::allowedIfHasPermission($account, $admin_permission);
}
else {
// No opinion.
return AccessResult::neutral();
}
}
/**
* Loads the current account object, if it does not exist yet.
*
* @param \Drupal\Core\Session\AccountInterface $account
* The account interface instance.
*
* @return \Drupal\Core\Session\AccountInterface
* Returns the current account object.
*/
protected function prepareUser(AccountInterface $account = NULL) {
if (!$account) {
$account = \Drupal::currentUser();
}
return $account;
}
/**
* {@inheritdoc}
*/
public function fieldAccess($operation, FieldDefinitionInterface $field_definition, AccountInterface $account = NULL, FieldItemListInterface $items = NULL, $return_as_object = FALSE) {
$account = $this->prepareUser($account);
// Get the default access restriction that lives within this field.
$default = $items ? $items->defaultAccess($operation, $account) : AccessResult::allowed();
// Get the default access restriction as specified by the access control
// handler.
$entity_default = $this->checkFieldAccess($operation, $field_definition, $account, $items);
// Combine default access, denying access wins.
$default = $default->andIf($entity_default);
// Invoke hook and collect grants/denies for field access from other
// modules. Our default access flag is masked under the ':default' key.
$grants = array(':default' => $default);
$hook_implementations = $this->moduleHandler()->getImplementations('entity_field_access');
foreach ($hook_implementations as $module) {
$grants = array_merge($grants, array($module => $this->moduleHandler()->invoke($module, 'entity_field_access', array($operation, $field_definition, $account, $items))));
}
// Also allow modules to alter the returned grants/denies.
$context = array(
'operation' => $operation,
'field_definition' => $field_definition,
'items' => $items,
'account' => $account,
);
$this->moduleHandler()->alter('entity_field_access', $grants, $context);
$result = $this->processAccessHookResults($grants);
return $return_as_object ? $result : $result->isAllowed();
}
/**
* Default field access as determined by this access control handler.
*
* @param string $operation
* The operation access should be checked for.
* Usually one of "view" or "edit".
* @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
* The field definition.
* @param \Drupal\Core\Session\AccountInterface $account
* The user session for which to check access.
* @param \Drupal\Core\Field\FieldItemListInterface $items
* (optional) The field values for which to check access, or NULL if access
* is checked for the field definition, without any specific value
* available. Defaults to NULL.
*
* @return bool
* TRUE if access is allowed, FALSE otherwise.
*/
protected function checkFieldAccess($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, FieldItemListInterface $items = NULL) {
return AccessResult::allowed();
}
}

View file

@ -0,0 +1,123 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\EntityAccessControlHandlerInterface.
*/
namespace Drupal\Core\Entity;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Session\AccountInterface;
/**
* Defines an interface for entity access control handlers.
*/
interface EntityAccessControlHandlerInterface {
/**
* Checks access to an operation on a given entity or entity translation.
*
* Use \Drupal\Core\Entity\EntityAccessControlHandlerInterface::createAccess()
* to check access to create an entity.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity for which to check access.
* @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.
* @param bool $return_as_object
* (optional) Defaults to FALSE.
*
* @return bool|\Drupal\Core\Access\AccessResultInterface
* The access result. Returns a boolean if $return_as_object is FALSE (this
* is the default) and otherwise an AccessResultInterface object.
* When a boolean is returned, the result of AccessInterface::isAllowed() is
* 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);
/**
* Checks access to create an entity.
*
* @param string $entity_bundle
* (optional) The bundle of the entity. Required if the entity supports
* bundles, defaults to NULL otherwise.
* @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.
* @param array $context
* (optional) An array of key-value pairs to pass additional context when
* needed.
* @param bool $return_as_object
* (optional) Defaults to FALSE.
*
* @return bool|\Drupal\Core\Access\AccessResultInterface
* The access result. Returns a boolean if $return_as_object is FALSE (this
* is the default) and otherwise an AccessResultInterface object.
* When a boolean is returned, the result of AccessInterface::isAllowed() is
* returned, i.e. TRUE means access is explicitly allowed, FALSE means
* access is either explicitly forbidden or "no opinion".
*/
public function createAccess($entity_bundle = NULL, AccountInterface $account = NULL, array $context = array(), $return_as_object = FALSE);
/**
* Clears all cached access checks.
*/
public function resetCache();
/**
* Sets the module handler for this access control handler.
*
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
*
* @return $this
*/
public function setModuleHandler(ModuleHandlerInterface $module_handler);
/**
* Checks access to an operation on a given entity field.
*
* This method does not determine whether access is granted to the entity
* itself, only the specific field. Callers are responsible for ensuring that
* entity access is also respected, for example by using
* \Drupal\Core\Entity\EntityAccessControlHandlerInterface::access().
*
* @param string $operation
* The operation access should be checked for.
* Usually one of "view" or "edit".
* @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
* The field definition.
* @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.
* @param \Drupal\Core\Field\FieldItemListInterface $items
* (optional) The field values for which to check access, or NULL if access
* is checked for the field definition, without any specific value
* available. Defaults to NULL.
* @param bool $return_as_object
* (optional) Defaults to FALSE.
*
* @return bool|\Drupal\Core\Access\AccessResultInterface
* The access result. Returns a boolean if $return_as_object is FALSE (this
* is the default) and otherwise an AccessResultInterface object.
* When a boolean is returned, the result of AccessInterface::isAllowed() is
* returned, i.e. TRUE means access is explicitly allowed, FALSE means
* access is either explicitly forbidden or "no opinion".
*
* @see \Drupal\Core\Entity\EntityAccessControlHandlerInterface::access()
*/
public function fieldAccess($operation, FieldDefinitionInterface $field_definition, AccountInterface $account = NULL, FieldItemListInterface $items = NULL, $return_as_object = FALSE);
}

View file

@ -0,0 +1,89 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\EntityAutocompleteMatcher.
*/
namespace Drupal\Core\Entity;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\Tags;
use Drupal\Core\Entity\EntityReferenceSelection\SelectionPluginManagerInterface;
/**
* Matcher class to get autocompletion results for entity reference.
*/
class EntityAutocompleteMatcher {
/**
* The entity reference selection handler plugin manager.
*
* @var \Drupal\Core\Entity\EntityReferenceSelection\SelectionPluginManagerInterface
*/
protected $selectionManager;
/**
* Constructs a EntityAutocompleteMatcher object.
*
* @param \Drupal\Core\Entity\EntityReferenceSelection\SelectionPluginManagerInterface $selection_manager
* The entity reference selection handler plugin manager.
*/
public function __construct(SelectionPluginManagerInterface $selection_manager) {
$this->selectionManager = $selection_manager;
}
/**
* Gets matched labels based on a given search string.
*
* @param string $target_type
* The ID of the target entity type.
* @param string $selection_handler
* The plugin ID of the entity reference selection handler.
* @param array $selection_settings
* An array of settings that will be passed to the selection handler.
* @param string $string
* (optional) The label of the entity to query by.
*
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
* Thrown when the current user doesn't have access to the specifies entity.
*
* @return array
* An array of matched entity labels, in the format required by the AJAX
* autocomplete API (e.g. array('value' => $value, 'label' => $label)).
*
* @see \Drupal\system\Controller\EntityAutocompleteController
*/
public function getMatches($target_type, $selection_handler, $selection_settings, $string = '') {
$matches = array();
$options = array(
'target_type' => $target_type,
'handler' => $selection_handler,
'handler_settings' => $selection_settings,
);
$handler = $this->selectionManager->getInstance($options);
if (isset($string)) {
// Get an array of matching entities.
$match_operator = !empty($selection_settings['match_operator']) ? $selection_settings['match_operator'] : 'CONTAINS';
$entity_labels = $handler->getReferenceableEntities($string, $match_operator, 10);
// Loop through the entities and convert them into autocomplete output.
foreach ($entity_labels as $values) {
foreach ($values as $entity_id => $label) {
$key = "$label ($entity_id)";
// Strip things like starting/trailing white spaces, line breaks and
// tags.
$key = preg_replace('/\s\s+/', ' ', str_replace("\n", '', trim(Html::decodeEntities(strip_tags($key)))));
// Names containing commas or quotes must be wrapped in quotes.
$key = Tags::encode($key);
$matches[] = array('value' => $key, 'label' => $label);
}
}
}
return $matches;
}
}

View file

@ -0,0 +1,53 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\EntityBundleListenerInterface.
*/
namespace Drupal\Core\Entity;
/**
* An interface for reacting to entity bundle creation, deletion, and renames.
*
* @todo Convert to Symfony events: https://www.drupal.org/node/2332935
*/
interface EntityBundleListenerInterface {
/**
* Reacts to a bundle being created.
*
* @param string $bundle
* The name of the bundle created.
* @param string $entity_type_id
* The entity type to which the bundle is bound; e.g. 'node' or 'user'.
*/
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.
*
* This method runs before fields are deleted.
*
* @param string $bundle
* The name of the bundle being deleted.
* @param string $entity_type_id
* The entity type to which the bundle is bound; e.g. 'node' or 'user'.
*/
public function onBundleDelete($bundle, $entity_type_id);
}

View file

@ -0,0 +1,40 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\EntityChangedInterface.
*/
namespace Drupal\Core\Entity;
/**
* Defines an interface for entity change timestamp tracking.
*
* This data may be useful for more precise cache invalidation (especially
* on the client side) and concurrent editing locking.
*
* The entity system automatically adds in the 'EntityChanged' constraint for
* entity types implementing this interface in order to disallow concurrent
* editing.
*
* @see Drupal\Core\Entity\Plugin\Validation\Constraint\EntityChangedConstraint
*/
interface EntityChangedInterface {
/**
* 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();
/**
* Gets the timestamp of the last entity change across all translations.
*
* @return int
* The timestamp of the last entity save operation across all
* translations.
*/
public function getChangedTimeAcrossTranslations();
}

View file

@ -0,0 +1,31 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\EntityChangedTrait.
*/
namespace Drupal\Core\Entity;
/**
* Provides a trait for accessing changed time.
*/
trait EntityChangedTrait {
/**
* Returns the timestamp of the last entity change across all translations.
*
* @return int
* The timestamp of the last entity save operation across all
* translations.
*/
public function getChangedTimeAcrossTranslations() {
$changed = $this->getUntranslated()->getChangedTime();
foreach ($this->getTranslationLanguages(FALSE) as $language) {
$translation_changed = $this->getTranslation($language->getId())->getChangedTime();
$changed = max($translation_changed, $changed);
}
return $changed;
}
}

View file

@ -0,0 +1,116 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\EntityConfirmFormBase.
*/
namespace Drupal\Core\Entity;
use Drupal\Core\Form\ConfirmFormHelper;
use Drupal\Core\Form\ConfirmFormInterface;
use Drupal\Core\Form\FormStateInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* Provides a generic base class for an entity-based confirmation form.
*
* @ingroup entity_api
*/
abstract class EntityConfirmFormBase extends EntityForm implements ConfirmFormInterface {
/**
* {@inheritdoc}
*/
public function getBaseFormId() {
return $this->entity->getEntityTypeId() . '_confirm_form';
}
/**
* {@inheritdoc}
*/
public function getDescription() {
return $this->t('This action cannot be undone.');
}
/**
* {@inheritdoc}
*/
public function getConfirmText() {
return $this->t('Confirm');
}
/**
* {@inheritdoc}
*/
public function getCancelText() {
return $this->t('Cancel');
}
/**
* {@inheritdoc}
*/
public function getFormName() {
return 'confirm';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form = parent::buildForm($form, $form_state);
$form['#title'] = $this->getQuestion();
$form['#attributes']['class'][] = 'confirmation';
$form['description'] = array('#markup' => $this->getDescription());
$form[$this->getFormName()] = array('#type' => 'hidden', '#value' => 1);
// By default, render the form using theme_confirm_form().
if (!isset($form['#theme'])) {
$form['#theme'] = 'confirm_form';
}
return $form;
}
/**
* {@inheritdoc}
*/
protected function actions(array $form, FormStateInterface $form_state) {
return array(
'submit' => array(
'#type' => 'submit',
'#value' => $this->getConfirmText(),
'#validate' => array(
array($this, 'validate'),
),
'#submit' => array(
array($this, 'submitForm'),
),
),
'cancel' => ConfirmFormHelper::buildCancelLink($this, $this->getRequest()),
);
}
/**
* {@inheritdoc}
*
* The save() method is not used in EntityConfirmFormBase. This overrides the
* default implementation that saves the entity.
*
* Confirmation forms should override submitForm() instead for their logic.
*/
public function save(array $form, FormStateInterface $form_state) {}
/**
* {@inheritdoc}
*
* The delete() method is not used in EntityConfirmFormBase. This overrides
* the default implementation that redirects to the delete-form confirmation
* form.
*
* Confirmation forms should override submitForm() instead for their logic.
*/
public function delete(array $form, FormStateInterface $form_state) {}
}

View file

@ -0,0 +1,215 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\EntityConstraintViolationList.
*/
namespace Drupal\Core\Entity;
use Drupal\Core\Entity\Plugin\Validation\Constraint\CompositeConstraintBase;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Component\Validator\ConstraintViolationInterface;
use Symfony\Component\Validator\ConstraintViolationList;
/**
* Implements an entity constraint violation list.
*/
class EntityConstraintViolationList extends ConstraintViolationList implements EntityConstraintViolationListInterface {
use StringTranslationTrait;
/**
* The entity that has been validated.
*
* @var \Drupal\Core\Entity\FieldableEntityInterface
*/
protected $entity;
/**
* Violations offsets of entity level violations.
*
* @var int[]|null
*/
protected $entityViolationOffsets;
/**
* Violation offsets grouped by field.
*
* Keys are field names, values are arrays of violation offsets.
*
* @var array[]|null
*/
protected $violationOffsetsByField;
/**
* {@inheritdoc}
*
* @param \Drupal\Core\Entity\FieldableEntityInterface $entity
* The entity that has been validated.
* @param array $violations
* The array of violations.
*/
public function __construct(FieldableEntityInterface $entity, array $violations = array()) {
parent::__construct($violations);
$this->entity = $entity;
}
/**
* Groups violation offsets by field and entity level.
*
* Sets the $violationOffsetsByField and $entityViolationOffsets properties.
*/
protected function groupViolationOffsets() {
if (!isset($this->violationOffsetsByField)) {
$this->violationOffsetsByField = [];
$this->entityViolationOffsets = [];
foreach ($this as $offset => $violation) {
if ($path = $violation->getPropertyPath()) {
// An example of $path might be 'title.0.value'.
list($field_name) = explode('.', $path, 2);
if ($this->entity->hasField($field_name)) {
$this->violationOffsetsByField[$field_name][$offset] = $offset;
}
}
else {
$this->entityViolationOffsets[$offset] = $offset;
}
}
}
}
/**
* {@inheritdoc}
*/
public function getEntityViolations() {
$this->groupViolationOffsets();
$violations = [];
foreach ($this->entityViolationOffsets as $offset) {
$violations[] = $this->get($offset);
}
return new static($this->entity, $violations);
}
/**
* {@inheritdoc}
*/
public function getByField($field_name) {
return $this->getByFields([$field_name]);
}
/**
* {@inheritdoc}
*/
public function getByFields(array $field_names) {
$this->groupViolationOffsets();
$violations = [];
foreach (array_intersect_key($this->violationOffsetsByField, array_flip($field_names)) as $field_name => $offsets) {
foreach ($offsets as $offset) {
$violations[] = $this->get($offset);
}
}
return new static($this->entity, $violations);
}
/**
* {@inheritdoc}
*/
public function filterByFields(array $field_names) {
$this->groupViolationOffsets();
$new_violations = [];
foreach (array_intersect_key($this->violationOffsetsByField, array_flip($field_names)) as $field_name => $offsets) {
foreach ($offsets as $offset) {
$violation = $this->get($offset);
// Take care of composite field violations and re-map them to some
// covered field if necessary.
if ($violation->getConstraint() instanceof CompositeConstraintBase) {
$covered_fields = $violation->getConstraint()->coversFields();
// Keep the composite field if it covers some remaining field and put
// a violation on some other covered field instead.
if ($remaining_fields = array_diff($covered_fields, $field_names)) {
$message_params = ['%field_name' => $field_name];
$violation = new ConstraintViolation(
$this->t('The validation failed because the value conflicts with the value in %field_name, which you cannot access.', $message_params),
'The validation failed because the value conflicts with the value in %field_name, which you cannot access.',
$message_params,
$violation->getRoot(),
reset($remaining_fields),
$violation->getInvalidValue(),
$violation->getPlural(),
$violation->getCode(),
$violation->getConstraint(),
$violation->getCause()
);
$new_violations[] = $violation;
}
}
$this->remove($offset);
}
}
foreach ($new_violations as $violation) {
$this->add($violation);
}
return $this;
}
/**
* {@inheritdoc}
*/
public function filterByFieldAccess(AccountInterface $account = NULL) {
$filtered_fields = array();
foreach ($this->getFieldNames() as $field_name) {
if (!$this->entity->get($field_name)->access('edit', $account)) {
$filtered_fields[] = $field_name;
}
}
return $this->filterByFields($filtered_fields);
}
/**
* {@inheritdoc}
*/
public function getFieldNames() {
$this->groupViolationOffsets();
return array_keys($this->violationOffsetsByField);
}
/**
* {@inheritdoc}
*/
public function getEntity() {
return $this->entity;
}
/**
* {@inheritdoc}
*/
public function add(ConstraintViolationInterface $violation) {
parent::add($violation);
$this->violationOffsetsByField = NULL;
$this->entityViolationOffsets = NULL;
}
/**
* {@inheritdoc}
*/
public function remove($offset) {
parent::remove($offset);
$this->violationOffsetsByField = NULL;
$this->entityViolationOffsets = NULL;
}
/**
* {@inheritdoc}
*/
public function set($offset, ConstraintViolationInterface $violation) {
parent::set($offset, $violation);
$this->violationOffsetsByField = NULL;
$this->entityViolationOffsets = NULL;
}
}

View file

@ -0,0 +1,100 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\EntityConstraintViolationListInterface.
*/
namespace Drupal\Core\Entity;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\Validator\ConstraintViolationListInterface;
/**
* Interface for the result of entity validation.
*
* The Symfony violation list is extended with methods that allow filtering
* violations by fields and field access. Forms leverage that to skip possibly
* pre-existing violations that cannot be caused or fixed by the form.
*/
interface EntityConstraintViolationListInterface extends ConstraintViolationListInterface {
/**
* Gets violations flagged on entity level, not associated with any field.
*
* @return \Drupal\Core\Entity\EntityConstraintViolationListInterface
* A list of violations on the entity level.
*/
public function getEntityViolations();
/**
* Gets the violations of the given field.
*
* @param string $field_name
* The name of the field to get violations for.
*
* @return \Symfony\Component\Validator\ConstraintViolationListInterface
* The violations of the given field.
*/
public function getByField($field_name);
/**
* Gets the violations of the given fields.
*
* When violations should be displayed for a sub-set of visible fields only,
* this method may be used to filter the set of visible violations first.
*
* @param string[] $field_names
* The names of the fields to get violations for.
*
* @return \Drupal\Core\Entity\EntityConstraintViolationListInterface
* A list of violations of the given fields.
*/
public function getByFields(array $field_names);
/**
* Filters this violation list by the given fields.
*
* The returned object just has violations attached to the provided fields.
*
* When violations should be displayed for a sub-set of visible fields only,
* this method may be used to filter the set of visible violations first.
*
* @param string[] $field_names
* The names of the fields to filter violations for.
*
* @return $this
*/
public function filterByFields(array $field_names);
/**
* Filters this violation list to apply for accessible fields only.
*
* Violations for inaccessible fields are removed so the returned object just
* has the remaining violations.
*
* @param \Drupal\Core\Session\AccountInterface $account
* (optional) The user for which to check access, or NULL to check access
* for the current user. Defaults to NULL.
*
* @return $this
*/
public function filterByFieldAccess(AccountInterface $account = NULL);
/**
* Returns the names of all violated fields.
*
* @return string[]
* An array of field names.
*/
public function getFieldNames();
/**
* The entity which has been validated.
*
* @return \Drupal\Core\Entity\FieldableEntityInterface
* The entity object.
*/
public function getEntity();
}

View file

@ -0,0 +1,76 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\EntityCreateAccessCheck.
*/
namespace Drupal\Core\Entity;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Routing\Access\AccessInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\Routing\Route;
/**
* Defines an access checker for entity creation.
*/
class EntityCreateAccessCheck implements AccessInterface {
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* The key used by the routing requirement.
*
* @var string
*/
protected $requirementsKey = '_entity_create_access';
/**
* Constructs a EntityCreateAccessCheck object.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
*/
public function __construct(EntityManagerInterface $entity_manager) {
$this->entityManager = $entity_manager;
}
/**
* Checks access to create the entity type and bundle for the given route.
*
* @param \Symfony\Component\Routing\Route $route
* The route to check against.
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* The parametrized route.
* @param \Drupal\Core\Session\AccountInterface $account
* The currently logged in account.
*
* @return \Drupal\Core\Access\AccessResultInterface
* The access result.
*/
public function access(Route $route, RouteMatchInterface $route_match, AccountInterface $account) {
list($entity_type, $bundle) = explode(':', $route->getRequirement($this->requirementsKey) . ':');
// The bundle argument can contain request argument placeholders like
// {name}, loop over the raw variables and attempt to replace them in the
// bundle name. If a placeholder does not exist, it won't get replaced.
if ($bundle && strpos($bundle, '{') !== FALSE) {
foreach ($route_match->getRawParameters()->all() as $name => $value) {
$bundle = str_replace('{' . $name . '}', $value, $bundle);
}
// If we were unable to replace all placeholders, deny access.
if (strpos($bundle, '{') !== FALSE) {
return AccessResult::neutral();
}
}
return $this->entityManager->getAccessControlHandler($entity_type)->createAccess($bundle, $account, [], TRUE);
}
}

View file

@ -0,0 +1,254 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\EntityDefinitionUpdateManager.
*/
namespace Drupal\Core\Entity;
use Drupal\Core\Entity\Schema\DynamicallyFieldableEntityStorageSchemaInterface;
use Drupal\Core\Entity\Schema\EntityStorageSchemaInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
/**
* Manages entity definition updates.
*/
class EntityDefinitionUpdateManager implements EntityDefinitionUpdateManagerInterface {
use StringTranslationTrait;
/**
* The entity manager service.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* Constructs a new EntityDefinitionUpdateManager.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
*/
public function __construct(EntityManagerInterface $entity_manager) {
$this->entityManager = $entity_manager;
}
/**
* {@inheritdoc}
*/
public function needsUpdates() {
return (bool) $this->getChangeList();
}
/**
* {@inheritdoc}
*/
public function getChangeSummary() {
$summary = array();
foreach ($this->getChangeList() as $entity_type_id => $change_list) {
// Process entity type definition changes.
if (!empty($change_list['entity_type'])) {
$entity_type = $this->entityManager->getDefinition($entity_type_id);
$t_args = array('%entity_type' => $entity_type->getLabel());
switch ($change_list['entity_type']) {
case static::DEFINITION_CREATED:
$summary[$entity_type_id][] = $this->t('Create the %entity_type entity type.', $t_args);
break;
case static::DEFINITION_UPDATED:
$summary[$entity_type_id][] = $this->t('Update the %entity_type entity type.', $t_args);
break;
}
}
// Process field storage definition changes.
if (!empty($change_list['field_storage_definitions'])) {
$storage_definitions = $this->entityManager->getFieldStorageDefinitions($entity_type_id);
$original_storage_definitions = $this->entityManager->getLastInstalledFieldStorageDefinitions($entity_type_id);
foreach ($change_list['field_storage_definitions'] as $field_name => $change) {
switch ($change) {
case static::DEFINITION_CREATED:
$summary[$entity_type_id][] = $this->t('Create the %field_name field.', array('%field_name' => $storage_definitions[$field_name]->getLabel()));
break;
case static::DEFINITION_UPDATED:
$summary[$entity_type_id][] = $this->t('Update the %field_name field.', array('%field_name' => $storage_definitions[$field_name]->getLabel()));
break;
case static::DEFINITION_DELETED:
$summary[$entity_type_id][] = $this->t('Delete the %field_name field.', array('%field_name' => $original_storage_definitions[$field_name]->getLabel()));
break;
}
}
}
}
return $summary;
}
/**
* {@inheritdoc}
*/
public function applyUpdates() {
$change_list = $this->getChangeList();
if ($change_list) {
// getChangeList() only disables the cache and does not invalidate.
// In case there are changes, explicitly invalidate caches.
$this->entityManager->clearCachedDefinitions();
}
foreach ($change_list as $entity_type_id => $change_list) {
// Process entity type definition changes before storage definitions ones
// this is necessary when you change an entity type from non-revisionable
// to revisionable and at the same time add revisionable fields to the
// entity type.
if (!empty($change_list['entity_type'])) {
$entity_type = $this->entityManager->getDefinition($entity_type_id);
switch ($change_list['entity_type']) {
case static::DEFINITION_CREATED:
$this->entityManager->onEntityTypeCreate($entity_type);
break;
case static::DEFINITION_UPDATED:
$original = $this->entityManager->getLastInstalledDefinition($entity_type_id);
$this->entityManager->onEntityTypeUpdate($entity_type, $original);
break;
}
}
// Process field storage definition changes.
if (!empty($change_list['field_storage_definitions'])) {
$storage_definitions = $this->entityManager->getFieldStorageDefinitions($entity_type_id);
$original_storage_definitions = $this->entityManager->getLastInstalledFieldStorageDefinitions($entity_type_id);
foreach ($change_list['field_storage_definitions'] as $field_name => $change) {
switch ($change) {
case static::DEFINITION_CREATED:
$this->entityManager->onFieldStorageDefinitionCreate($storage_definitions[$field_name]);
break;
case static::DEFINITION_UPDATED:
$this->entityManager->onFieldStorageDefinitionUpdate($storage_definitions[$field_name], $original_storage_definitions[$field_name]);
break;
case static::DEFINITION_DELETED:
$this->entityManager->onFieldStorageDefinitionDelete($original_storage_definitions[$field_name]);
break;
}
}
}
}
}
/**
* Gets a list of changes to entity type and field storage definitions.
*
* @return array
* An associative array keyed by entity type id of change descriptors. Every
* entry is an associative array with the following optional keys:
* - entity_type: a scalar having only the DEFINITION_UPDATED value.
* - field_storage_definitions: an associative array keyed by field name of
* scalars having one value among:
* - DEFINITION_CREATED
* - DEFINITION_UPDATED
* - DEFINITION_DELETED
*/
protected function getChangeList() {
$this->entityManager->useCaches(FALSE);
$change_list = array();
foreach ($this->entityManager->getDefinitions() as $entity_type_id => $entity_type) {
$original = $this->entityManager->getLastInstalledDefinition($entity_type_id);
// @todo Support non-storage-schema-changing definition updates too:
// https://www.drupal.org/node/2336895.
if (!$original) {
$change_list[$entity_type_id]['entity_type'] = static::DEFINITION_CREATED;
}
else {
if ($this->requiresEntityStorageSchemaChanges($entity_type, $original)) {
$change_list[$entity_type_id]['entity_type'] = static::DEFINITION_UPDATED;
}
if ($this->entityManager->getStorage($entity_type_id) instanceof DynamicallyFieldableEntityStorageInterface) {
$field_changes = array();
$storage_definitions = $this->entityManager->getFieldStorageDefinitions($entity_type_id);
$original_storage_definitions = $this->entityManager->getLastInstalledFieldStorageDefinitions($entity_type_id);
// Detect created field storage definitions.
foreach (array_diff_key($storage_definitions, $original_storage_definitions) as $field_name => $storage_definition) {
$field_changes[$field_name] = static::DEFINITION_CREATED;
}
// Detect deleted field storage definitions.
foreach (array_diff_key($original_storage_definitions, $storage_definitions) as $field_name => $original_storage_definition) {
$field_changes[$field_name] = static::DEFINITION_DELETED;
}
// Detect updated field storage definitions.
foreach (array_intersect_key($storage_definitions, $original_storage_definitions) as $field_name => $storage_definition) {
// @todo Support non-storage-schema-changing definition updates too:
// https://www.drupal.org/node/2336895. So long as we're checking
// based on schema change requirements rather than definition
// equality, skip the check if the entity type itself needs to be
// updated, since that can affect the schema of all fields, so we
// want to process that update first without reporting false
// positives here.
if (!isset($change_list[$entity_type_id]['entity_type']) && $this->requiresFieldStorageSchemaChanges($storage_definition, $original_storage_definitions[$field_name])) {
$field_changes[$field_name] = static::DEFINITION_UPDATED;
}
}
if ($field_changes) {
$change_list[$entity_type_id]['field_storage_definitions'] = $field_changes;
}
}
}
}
// @todo Support deleting entity definitions when we support base field
// purging. See https://www.drupal.org/node/2282119.
$this->entityManager->useCaches(TRUE);
return array_filter($change_list);
}
/**
* Checks if the changes to the entity type requires storage schema changes.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The updated entity type definition.
* @param \Drupal\Core\Entity\EntityTypeInterface $original
* The original entity type definition.
*
* @return bool
* TRUE if storage schema changes are required, FALSE otherwise.
*/
protected function requiresEntityStorageSchemaChanges(EntityTypeInterface $entity_type, EntityTypeInterface $original) {
$storage = $this->entityManager->getStorage($entity_type->id());
return ($storage instanceof EntityStorageSchemaInterface) && $storage->requiresEntityStorageSchemaChanges($entity_type, $original);
}
/**
* Checks if the changes to the storage definition requires schema changes.
*
* @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
* The updated field storage definition.
* @param \Drupal\Core\Field\FieldStorageDefinitionInterface $original
* The original field storage definition.
*
* @return bool
* TRUE if storage schema changes are required, FALSE otherwise.
*/
protected function requiresFieldStorageSchemaChanges(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original) {
$storage = $this->entityManager->getStorage($storage_definition->getTargetEntityTypeId());
return ($storage instanceof DynamicallyFieldableEntityStorageSchemaInterface) && $storage->requiresFieldStorageSchemaChanges($storage_definition, $original);
}
}

View file

@ -0,0 +1,86 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface.
*/
namespace Drupal\Core\Entity;
/**
* Defines an interface for managing entity definition updates.
*
* During the application lifetime, the definitions of various entity types and
* their data components (e.g., fields for fieldable entity types) can change.
* For example, updated code can be deployed. Some entity handlers may need to
* perform complex or long-running logic in response to the change. For
* example, a SQL-based storage handler may need to update the database schema.
*
* To support this, \Drupal\Core\Entity\EntityManagerInterface has methods to
* retrieve the last installed definitions as well as the definitions specified
* by the current codebase. It also has create/update/delete methods to bring
* the former up to date with the latter.
*
* However, it is not the responsibility of the entity manager to decide how to
* report the differences or when to apply each update. This interface is for
* managing that.
*
* @see \Drupal\Core\Entity\EntityManagerInterface::getDefinition()
* @see \Drupal\Core\Entity\EntityManagerInterface::getLastInstalledDefinition()
* @see \Drupal\Core\Entity\EntityManagerInterface::getFieldStorageDefinitions()
* @see \Drupal\Core\Entity\EntityManagerInterface::getLastInstalledFieldStorageDefinitions()
* @see \Drupal\Core\Entity\EntityTypeListenerInterface
* @see \Drupal\Core\Field\FieldStorageDefinitionListenerInterface
*/
interface EntityDefinitionUpdateManagerInterface {
/**
* Indicates that a definition has just been created.
*
* @var int
*/
const DEFINITION_CREATED = 1;
/**
* Indicates that a definition has changes.
*
* @var int
*/
const DEFINITION_UPDATED = 2;
/**
* Indicates that a definition has just been deleted.
*
* @var int
*/
const DEFINITION_DELETED = 3;
/**
* Checks if there are any definition updates that need to be applied.
*
* @return bool
* TRUE if updates are needed.
*/
public function needsUpdates();
/**
* Gets a human readable summary of the detected changes.
*
* @return array
* An associative array keyed by entity type id. Each entry is an array of
* human-readable strings, each describing a change.
*/
public function getChangeSummary();
/**
* Applies all the detected valid changes.
*
* @throws \Drupal\Core\Entity\EntityStorageException
* This exception is thrown if a change cannot be applied without
* unacceptable data loss. In such a case, the site administrator needs to
* apply some other process, such as a custom update function or a
* migration via the Migrate module.
*/
public function applyUpdates();
}

View file

@ -0,0 +1,52 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\EntityDeleteForm.
*/
namespace Drupal\Core\Entity;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
use Drupal\Core\Form\FormStateInterface;
/**
* Provides a generic base class for an entity deletion form.
*
* @ingroup entity_api
*/
class EntityDeleteForm extends EntityConfirmFormBase {
use EntityDeleteFormTrait;
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form = parent::buildForm($form, $form_state);
$entity = $this->getEntity();
// Only do dependency processing for configuration entities. Whilst it is
// possible for a configuration entity to be dependent on a content entity,
// these dependencies are soft and content delete permissions are often
// given to more users. This method should not make assumptions that $entity
// is a configuration entity in case we decide to remove the following
// condition.
if (!($entity instanceof ConfigEntityInterface)) {
return $form;
}
$this->addDependencyListsToForm($form, $entity->getConfigDependencyKey(), [$entity->getConfigDependencyName()], $this->getConfigManager(), $this->entityManager);
return $form;
}
/**
* Gets the configuration manager.
*
* @return \Drupal\Core\Config\ConfigManager
* The configuration manager.
*/
protected function getConfigManager() {
return \Drupal::service('config.manager');
}
}

View file

@ -0,0 +1,133 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\EntityDeleteFormTrait.
*/
namespace Drupal\Core\Entity;
use Drupal\Core\Config\Entity\ConfigDependencyDeleteFormTrait;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
/**
* Provides a trait for an entity deletion form.
*
* This trait relies on the StringTranslationTrait and the logger method added
* by FormBase.
*
* @ingroup entity_api
*/
trait EntityDeleteFormTrait {
use ConfigDependencyDeleteFormTrait;
/**
* Gets the entity of this form.
*
* Provided by \Drupal\Core\Entity\EntityForm.
*
* @return \Drupal\Core\Entity\EntityInterface
* The entity.
*/
abstract public function getEntity();
/**
* Gets the logger for a specific channel.
*
* Provided by \Drupal\Core\Form\FormBase.
*
* @param string $channel
* The name of the channel.
*
* @return \Psr\Log\LoggerInterface
* The logger for this channel.
*/
abstract protected function logger($channel);
/**
* {@inheritdoc}
*/
public function getQuestion() {
return $this->t('Are you sure you want to delete the @entity-type %label?', array(
'@entity-type' => $this->getEntity()->getEntityType()->getLowercaseLabel(),
'%label' => $this->getEntity()->label(),
));
}
/**
* {@inheritdoc}
*/
public function getConfirmText() {
return $this->t('Delete');
}
/**
* Gets the message to display to the user after deleting the entity.
*
* @return string
* The translated string of the deletion message.
*/
protected function getDeletionMessage() {
$entity = $this->getEntity();
return $this->t('The @entity-type %label has been deleted.', array(
'@entity-type' => $entity->getEntityType()->getLowercaseLabel(),
'%label' => $entity->label(),
));
}
/**
* {@inheritdoc}
*/
public function getCancelUrl() {
$entity = $this->getEntity();
if ($entity->hasLinkTemplate('collection')) {
// If available, return the collection URL.
return $entity->urlInfo('collection');
}
else {
// Otherwise fall back to the default link template.
return $entity->urlInfo();
}
}
/**
* Returns the URL where the user should be redirected after deletion.
*
* @return \Drupal\Core\Url
* The redirect URL.
*/
protected function getRedirectUrl() {
$entity = $this->getEntity();
if ($entity->hasLinkTemplate('collection')) {
// If available, return the collection URL.
return $entity->urlInfo('collection');
}
else {
// Otherwise fall back to the front page.
return Url::fromRoute('<front>');
}
}
/**
* Logs a message about the deleted entity.
*/
protected function logDeletionMessage() {
$entity = $this->getEntity();
$this->logger($entity->getEntityType()->getProvider())->notice('The @entity-type %label has been deleted.', array(
'@entity-type' => $entity->getEntityType()->getLowercaseLabel(),
'%label' => $entity->label(),
));
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$this->getEntity()->delete();
drupal_set_message($this->getDeletionMessage());
$form_state->setRedirectUrl($this->getCancelUrl());
$this->logDeletionMessage();
}
}

View file

@ -0,0 +1,485 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\EntityDisplayBase.
*/
namespace Drupal\Core\Entity;
use Drupal\Core\Config\Entity\ConfigEntityBase;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Entity\Display\EntityDisplayInterface;
use Drupal\Component\Utility\SafeMarkup;
/**
* Provides a common base class for entity view and form displays.
*/
abstract class EntityDisplayBase extends ConfigEntityBase implements EntityDisplayInterface {
/**
* The 'mode' for runtime EntityDisplay objects used to render entities with
* arbitrary display options rather than a configured view mode or form mode.
*
* @todo Prevent creation of a mode with this ID
* https://www.drupal.org/node/2410727
*/
const CUSTOM_MODE = '_custom';
/**
* Unique ID for the config entity.
*
* @var string
*/
protected $id;
/**
* Entity type to be displayed.
*
* @var string
*/
protected $targetEntityType;
/**
* Bundle to be displayed.
*
* @var string
*/
protected $bundle;
/**
* A list of field definitions eligible for configuration in this display.
*
* @var \Drupal\Core\Field\FieldDefinitionInterface[]
*/
protected $fieldDefinitions;
/**
* View or form mode to be displayed.
*
* @var string
*/
protected $mode = self::CUSTOM_MODE;
/**
* Whether this display is enabled or not. If the entity (form) display
* is disabled, we'll fall back to the 'default' display.
*
* @var bool
*/
protected $status;
/**
* List of component display options, keyed by component name.
*
* @var array
*/
protected $content = array();
/**
* List of components that are set to be hidden.
*
* @var array
*/
protected $hidden = array();
/**
* The original view or form mode that was requested (case of view/form modes
* being configured to fall back to the 'default' display).
*
* @var string
*/
protected $originalMode;
/**
* The plugin objects used for this display, keyed by field name.
*
* @var array
*/
protected $plugins = array();
/**
* Context in which this entity will be used (e.g. 'display', 'form').
*
* @var string
*/
protected $displayContext;
/**
* The plugin manager used by this entity type.
*
* @var \Drupal\Component\Plugin\PluginManagerBase
*/
protected $pluginManager;
/**
* The renderer.
*
* @var \Drupal\Core\Render\RendererInterface
*/
protected $renderer;
/**
* {@inheritdoc}
*/
public function __construct(array $values, $entity_type) {
if (!isset($values['targetEntityType']) || !isset($values['bundle'])) {
throw new \InvalidArgumentException('Missing required properties for an EntityDisplay entity.');
}
if (!$this->entityManager()->getDefinition($values['targetEntityType'])->isSubclassOf('\Drupal\Core\Entity\FieldableEntityInterface')) {
throw new \InvalidArgumentException('EntityDisplay entities can only handle fieldable entity types.');
}
$this->renderer = \Drupal::service('renderer');
// A plugin manager and a context type needs to be set by extending classes.
if (!isset($this->pluginManager)) {
throw new \RuntimeException('Missing plugin manager.');
}
if (!isset($this->displayContext)) {
throw new \RuntimeException('Missing display context type.');
}
parent::__construct($values, $entity_type);
$this->originalMode = $this->mode;
$this->init();
}
/**
* Initializes the display.
*
* This fills in default options for components:
* - that are not explicitly known as either "visible" or "hidden" in the
* display,
* - or that are not supposed to be configurable.
*/
protected function init() {
// Only populate defaults for "official" view modes and form modes.
if ($this->mode !== static::CUSTOM_MODE) {
// Fill in defaults for extra fields.
$context = $this->displayContext == 'view' ? 'display' : $this->displayContext;
$extra_fields = \Drupal::entityManager()->getExtraFields($this->targetEntityType, $this->bundle);
$extra_fields = isset($extra_fields[$context]) ? $extra_fields[$context] : array();
foreach ($extra_fields as $name => $definition) {
if (!isset($this->content[$name]) && !isset($this->hidden[$name])) {
// Extra fields are visible by default unless they explicitly say so.
if (!isset($definition['visible']) || $definition['visible'] == TRUE) {
$this->content[$name] = array(
'weight' => $definition['weight']
);
}
else {
$this->hidden[$name] = TRUE;
}
}
}
// Fill in defaults for fields.
$fields = $this->getFieldDefinitions();
foreach ($fields as $name => $definition) {
if (!$definition->isDisplayConfigurable($this->displayContext) || (!isset($this->content[$name]) && !isset($this->hidden[$name]))) {
$options = $definition->getDisplayOptions($this->displayContext);
if (!empty($options['type']) && $options['type'] == 'hidden') {
$this->hidden[$name] = TRUE;
}
elseif ($options) {
$this->content[$name] = $this->pluginManager->prepareConfiguration($definition->getType(), $options);
}
// Note: (base) fields that do not specify display options are not
// tracked in the display at all, in order to avoid cluttering the
// configuration that gets saved back.
}
}
}
}
/**
* {@inheritdoc}
*/
public function getTargetEntityTypeId() {
return $this->targetEntityType;
}
/**
* {@inheritdoc}
*/
public function getMode() {
return $this->get('mode');
}
/**
* {@inheritdoc}
*/
public function getOriginalMode() {
return $this->get('originalMode');
}
/**
* {@inheritdoc}
*/
public function getTargetBundle() {
return $this->bundle;
}
/**
* {@inheritdoc}
*/
public function setTargetBundle($bundle) {
$this->set('bundle', $bundle);
return $this;
}
/**
* {@inheritdoc}
*/
public function id() {
return $this->targetEntityType . '.' . $this->bundle . '.' . $this->mode;
}
/**
* {@inheritdoc}
*/
public function preSave(EntityStorageInterface $storage, $update = TRUE) {
ksort($this->content);
ksort($this->hidden);
parent::preSave($storage, $update);
}
/**
* {@inheritdoc}
*/
public function calculateDependencies() {
parent::calculateDependencies();
$target_entity_type = $this->entityManager()->getDefinition($this->targetEntityType);
$bundle_entity_type_id = $target_entity_type->getBundleEntityType();
if ($bundle_entity_type_id != 'bundle') {
// If the target entity type uses entities to manage its bundles then
// depend on the bundle entity.
if (!$bundle_entity = $this->entityManager()->getStorage($bundle_entity_type_id)->load($this->bundle)) {
throw new \LogicException(SafeMarkup::format('Missing bundle entity, entity type %type, entity id %bundle.', array('%type' => $bundle_entity_type_id, '%bundle' => $this->bundle)));
}
$this->addDependency('config', $bundle_entity->getConfigDependencyName());
}
else {
// Depend on the provider of the entity type.
$this->addDependency('module', $target_entity_type->getProvider());
}
// If field.module is enabled, add dependencies on 'field_config' entities
// for both displayed and hidden fields. We intentionally leave out base
// field overrides, since the field still exists without them.
if (\Drupal::moduleHandler()->moduleExists('field')) {
$components = $this->content + $this->hidden;
$field_definitions = $this->entityManager()->getFieldDefinitions($this->targetEntityType, $this->bundle);
foreach (array_intersect_key($field_definitions, $components) as $field_name => $field_definition) {
if ($field_definition instanceof ConfigEntityInterface && $field_definition->getEntityTypeId() == 'field_config') {
$this->addDependency('config', $field_definition->getConfigDependencyName());
}
}
}
// Depend on configured modes.
if ($this->mode != 'default') {
$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;
}
/**
* {@inheritdoc}
*/
public function toArray() {
$properties = parent::toArray();
// Do not store options for fields whose display is not set to be
// configurable.
foreach ($this->getFieldDefinitions() as $field_name => $definition) {
if (!$definition->isDisplayConfigurable($this->displayContext)) {
unset($properties['content'][$field_name]);
unset($properties['hidden'][$field_name]);
}
}
return $properties;
}
/**
* {@inheritdoc}
*/
public function createCopy($mode) {
$display = $this->createDuplicate();
$display->mode = $display->originalMode = $mode;
return $display;
}
/**
* {@inheritdoc}
*/
public function getComponents() {
return $this->content;
}
/**
* {@inheritdoc}
*/
public function getComponent($name) {
return isset($this->content[$name]) ? $this->content[$name] : NULL;
}
/**
* {@inheritdoc}
*/
public function setComponent($name, array $options = array()) {
// If no weight specified, make sure the field sinks at the bottom.
if (!isset($options['weight'])) {
$max = $this->getHighestWeight();
$options['weight'] = isset($max) ? $max + 1 : 0;
}
// For a field, fill in default options.
if ($field_definition = $this->getFieldDefinition($name)) {
$options = $this->pluginManager->prepareConfiguration($field_definition->getType(), $options);
}
// Ensure we always have an empty settings and array.
$options += ['settings' => [], 'third_party_settings' => []];
$this->content[$name] = $options;
unset($this->hidden[$name]);
unset($this->plugins[$name]);
return $this;
}
/**
* {@inheritdoc}
*/
public function removeComponent($name) {
$this->hidden[$name] = TRUE;
unset($this->content[$name]);
unset($this->plugins[$name]);
return $this;
}
/**
* {@inheritdoc}
*/
public function getHighestWeight() {
$weights = array();
// Collect weights for the components in the display.
foreach ($this->content as $options) {
if (isset($options['weight'])) {
$weights[] = $options['weight'];
}
}
// Let other modules feedback about their own additions.
$weights = array_merge($weights, \Drupal::moduleHandler()->invokeAll('field_info_max_weight', array($this->targetEntityType, $this->bundle, $this->displayContext, $this->mode)));
return $weights ? max($weights) : NULL;
}
/**
* Gets the field definition of a field.
*/
protected function getFieldDefinition($field_name) {
$definitions = $this->getFieldDefinitions();
return isset($definitions[$field_name]) ? $definitions[$field_name] : NULL;
}
/**
* Gets the definitions of the fields that are candidate for display.
*/
protected function getFieldDefinitions() {
if (!isset($this->fieldDefinitions)) {
$definitions = \Drupal::entityManager()->getFieldDefinitions($this->targetEntityType, $this->bundle);
// For "official" view modes and form modes, ignore fields whose
// definition states they should not be displayed.
if ($this->mode !== static::CUSTOM_MODE) {
$definitions = array_filter($definitions, array($this, 'fieldHasDisplayOptions'));
}
$this->fieldDefinitions = $definitions;
}
return $this->fieldDefinitions;
}
/**
* Determines if a field has options for a given display.
*
* @param FieldDefinitionInterface $definition
* A field definition.
* @return array|null
*/
private function fieldHasDisplayOptions(FieldDefinitionInterface $definition) {
// The display only cares about fields that specify display options.
// Discard base fields that are not rendered through formatters / widgets.
return $definition->getDisplayOptions($this->displayContext);
}
/**
* {@inheritdoc}
*/
public function onDependencyRemoval(array $dependencies) {
$changed = parent::onDependencyRemoval($dependencies);
foreach ($dependencies['config'] as $entity) {
if ($entity->getEntityTypeId() == 'field_config') {
// Remove components for fields that are being deleted.
$this->removeComponent($entity->getName());
unset($this->hidden[$entity->getName()]);
$changed = TRUE;
}
}
foreach ($this->getComponents() as $name => $component) {
if (isset($component['type']) && $definition = $this->pluginManager->getDefinition($component['type'], FALSE)) {
if (in_array($definition['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;
}
}
}
return $changed;
}
/**
* {@inheritdoc}
*/
public function __sleep() {
// Only store the definition, not external objects or derived data.
$keys = array_keys($this->toArray());
// In addition, we need to keep the entity type and the "is new" status.
$keys[] = 'entityTypeId';
$keys[] = 'enforceIsNew';
// Keep track of the serialized keys, to avoid calling toArray() again in
// __wakeup(). Because of the way __sleep() works, the data has to be
// present in the object to be included in the serialized values.
$keys[] = '_serializedKeys';
$this->_serializedKeys = $keys;
return $keys;
}
/**
* {@inheritdoc}
*/
public function __wakeup() {
// Determine what were the properties from toArray() that were saved in
// __sleep().
$keys = $this->_serializedKeys;
unset($this->_serializedKeys);
$values = array_intersect_key(get_object_vars($this), array_flip($keys));
// Run those values through the __construct(), as if they came from a
// regular entity load.
$this->__construct($values, $this->entityTypeId);
}
}

View file

@ -0,0 +1,115 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\EntityDisplayModeBase.
*/
namespace Drupal\Core\Entity;
use Drupal\Core\Config\Entity\ConfigEntityBase;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
/**
* Base class for config entity types with settings for form and view modes.
*/
abstract class EntityDisplayModeBase extends ConfigEntityBase implements EntityDisplayModeInterface {
/**
* The ID of the form or view mode.
*
* @var string
*/
protected $id;
/**
* The human-readable name of the form or view mode.
*
* @var string
*/
protected $label;
/**
* The entity type this form or view mode is used for.
*
* This is not to be confused with EntityDisplayModeBase::$entityType which is
* inherited from Entity::$entityType.
*
* @var string
*/
protected $targetEntityType;
/**
* Whether or not this form or view mode has custom settings by default.
*
* If FALSE, entities displayed in this mode will reuse the 'default' display
* settings by default (e.g. right after the module exposing the form or view
* mode is enabled), but administrators can later use the Field UI to apply
* custom display settings specific to the form or view mode.
*
* @var bool
*/
protected $status = TRUE;
/**
* Whether or not the rendered output of this view mode is cached by default.
*
* @var bool
*/
protected $cache = TRUE;
/**
* {@inheritdoc}
*/
public static function sort(ConfigEntityInterface $a, ConfigEntityInterface $b) {
/** @var \Drupal\Core\Entity\EntityDisplayModeInterface $a */
/** @var \Drupal\Core\Entity\EntityDisplayModeInterface $b */
// Sort by the type of entity the view mode is used for.
$a_type = $a->getTargetType();
$b_type = $b->getTargetType();
$type_order = strnatcasecmp($a_type, $b_type);
return $type_order != 0 ? $type_order : parent::sort($a, $b);
}
/**
* {@inheritdoc}
*/
public function getTargetType() {
return $this->targetEntityType;
}
/**
* {@inheritdoc}
*/
public function setTargetType($target_entity_type) {
$this->targetEntityType = $target_entity_type;
return $this;
}
/**
* {@inheritdoc}
*/
public function calculateDependencies() {
parent::calculateDependencies();
$target_entity_type = \Drupal::entityManager()->getDefinition($this->targetEntityType);
$this->addDependency('module', $target_entity_type->getProvider());
return $this->dependencies;
}
/**
* {@inheritdoc}
*/
public function preSave(EntityStorageInterface $storage) {
parent::preSave($storage);
\Drupal::entityManager()->clearCachedFieldDefinitions();
}
/**
* {@inheritdoc}
*/
public static function preDelete(EntityStorageInterface $storage, array $entities) {
parent::preDelete($storage, $entities);
\Drupal::entityManager()->clearCachedFieldDefinitions();
}
}

View file

@ -0,0 +1,34 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\EntityDisplayModeInterface.
*/
namespace Drupal\Core\Entity;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
/**
* Provides an interface for entity types that hold form and view mode settings.
*/
interface EntityDisplayModeInterface extends ConfigEntityInterface {
/**
* Gets the entity type this display mode is used for.
*
* @return string
* The entity type name.
*/
public function getTargetType();
/**
* Set the entity type this display mode is used for.
*
* @param string $target_entity_type
* The target entity type for this display mode.
*
* @return $this
*/
public function setTargetType($target_entity_type);
}

View file

@ -0,0 +1,24 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\EntityDisplayPluginCollection.
*/
namespace Drupal\Core\Entity;
use Drupal\Core\Plugin\DefaultLazyPluginCollection;
/**
* A collection of formatters or widgets.
*/
class EntityDisplayPluginCollection extends DefaultLazyPluginCollection {
/**
* The key within the plugin configuration that contains the plugin ID.
*
* @var string
*/
protected $pluginKey = 'type';
}

View file

@ -0,0 +1,412 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\EntityForm.
*/
namespace Drupal\Core\Entity;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Routing\RouteMatchInterface;
/**
* Base class for entity forms.
*
* @ingroup entity_api
*/
class EntityForm extends FormBase implements EntityFormInterface {
/**
* The name of the current operation.
*
* Subclasses may use this to implement different behaviors depending on its
* value.
*
* @var string
*/
protected $operation;
/**
* The module handler service.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* The entity being used by this form.
*
* @var \Drupal\Core\Entity\EntityInterface
*/
protected $entity;
/**
* {@inheritdoc}
*/
public function setOperation($operation) {
// If NULL is passed, do not overwrite the operation.
if ($operation) {
$this->operation = $operation;
}
return $this;
}
/**
* {@inheritdoc}
*/
public function getBaseFormId() {
// Assign ENTITYTYPE_form as base form ID to invoke corresponding
// hook_form_alter(), #validate, #submit, and #theme callbacks, but only if
// it is different from the actual form ID, since callbacks would be invoked
// twice otherwise.
$base_form_id = $this->entity->getEntityTypeId() . '_form';
if ($base_form_id == $this->getFormId()) {
$base_form_id = NULL;
}
return $base_form_id;
}
/**
* {@inheritdoc}
*/
public function getFormId() {
$form_id = $this->entity->getEntityTypeId();
if ($this->entity->getEntityType()->hasKey('bundle')) {
$form_id .= '_' . $this->entity->bundle();
}
if ($this->operation != 'default') {
$form_id = $form_id . '_' . $this->operation;
}
return $form_id . '_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
// During the initial form build, add this form object to the form state and
// allow for initial preparation before form building and processing.
if (!$form_state->has('entity_form_initialized')) {
$this->init($form_state);
}
// Retrieve the form array using the possibly updated entity in form state.
$form = $this->form($form, $form_state);
// Retrieve and add the form actions array.
$actions = $this->actionsElement($form, $form_state);
if (!empty($actions)) {
$form['actions'] = $actions;
}
return $form;
}
/**
* Initialize the form state and the entity before the first form build.
*/
protected function init(FormStateInterface $form_state) {
// Flag that this form has been initialized.
$form_state->set('entity_form_initialized', TRUE);
// Prepare the entity to be presented in the entity form.
$this->prepareEntity();
// Invoke the prepare form hooks.
$this->prepareInvokeAll('entity_prepare_form', $form_state);
$this->prepareInvokeAll($this->entity->getEntityTypeId() . '_prepare_form', $form_state);
}
/**
* Gets the actual form array to be built.
*
* @see \Drupal\Core\Entity\EntityForm::processForm()
* @see \Drupal\Core\Entity\EntityForm::afterBuild()
*/
public function form(array $form, FormStateInterface $form_state) {
// Add #process and #after_build callbacks.
$form['#process'][] = '::processForm';
$form['#after_build'][] = '::afterBuild';
return $form;
}
/**
* Process callback: assigns weights and hides extra fields.
*
* @see \Drupal\Core\Entity\EntityForm::form()
*/
public function processForm($element, FormStateInterface $form_state, $form) {
// If the form is cached, process callbacks may not have a valid reference
// to the entity object, hence we must restore it.
$this->entity = $form_state->getFormObject()->getEntity();
return $element;
}
/**
* Form element #after_build callback: Updates the entity with submitted data.
*
* Updates the internal $this->entity object with submitted values when the
* form is being rebuilt (e.g. submitted via AJAX), so that subsequent
* processing (e.g. AJAX callbacks) can rely on it.
*/
public function afterBuild(array $element, FormStateInterface $form_state) {
// Rebuild the entity if #after_build is being called as part of a form
// rebuild, i.e. if we are processing input.
if ($form_state->isProcessingInput()) {
$this->entity = $this->buildEntity($element, $form_state);
}
return $element;
}
/**
* Returns the action form element for the current entity form.
*/
protected function actionsElement(array $form, FormStateInterface $form_state) {
$element = $this->actions($form, $form_state);
if (isset($element['delete'])) {
// Move the delete action as last one, unless weights are explicitly
// provided.
$delete = $element['delete'];
unset($element['delete']);
$element['delete'] = $delete;
$element['delete']['#button_type'] = 'danger';
}
if (isset($element['submit'])) {
// Give the primary submit button a #button_type of primary.
$element['submit']['#button_type'] = 'primary';
}
$count = 0;
foreach (Element::children($element) as $action) {
$element[$action] += array(
'#weight' => ++$count * 5,
);
}
if (!empty($element)) {
$element['#type'] = 'actions';
}
return $element;
}
/**
* Returns an array of supported actions for the current entity form.
*
* @todo Consider introducing a 'preview' action here, since it is used by
* many entity types.
*/
protected function actions(array $form, FormStateInterface $form_state) {
// @todo Consider renaming the action key from submit to save. The impacts
// are hard to predict. For example, see
// \Drupal\language\Element\LanguageConfiguration::processLanguageConfiguration().
$actions['submit'] = array(
'#type' => 'submit',
'#value' => $this->t('Save'),
'#validate' => array('::validate'),
'#submit' => array('::submitForm', '::save'),
);
if (!$this->entity->isNew() && $this->entity->hasLinkTemplate('delete-form')) {
$route_info = $this->entity->urlInfo('delete-form');
if ($this->getRequest()->query->has('destination')) {
$query = $route_info->getOption('query');
$query['destination'] = $this->getRequest()->query->get('destination');
$route_info->setOption('query', $query);
}
$actions['delete'] = array(
'#type' => 'link',
'#title' => $this->t('Delete'),
'#access' => $this->entity->access('delete'),
'#attributes' => array(
'class' => array('button', 'button--danger'),
),
);
$actions['delete']['#url'] = $route_info;
}
return $actions;
}
/**
* {@inheritdoc}
*/
public function validate(array $form, FormStateInterface $form_state) {
// @todo Remove this.
// Execute legacy global validation handlers.
$form_state->setValidateHandlers([]);
\Drupal::service('form_validator')->executeValidateHandlers($form, $form_state);
}
/**
* {@inheritdoc}
*
* This is the default entity object builder function. It is called before any
* other submit handler to build the new entity object to be used by the
* following submit handlers. At this point of the form workflow the entity is
* validated and the form state can be updated, this way the subsequently
* invoked handlers can retrieve a regular entity object to act on. Generally
* this method should not be overridden unless the entity requires the same
* preparation for two actions, see \Drupal\comment\CommentForm for an example
* with the save and preview actions.
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
// Remove button and internal Form API values from submitted values.
$form_state->cleanValues();
$this->entity = $this->buildEntity($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function save(array $form, FormStateInterface $form_state) {
return $this->entity->save();
}
/**
* {@inheritdoc}
*/
public function buildEntity(array $form, FormStateInterface $form_state) {
$entity = clone $this->entity;
$this->copyFormValuesToEntity($entity, $form, $form_state);
// Invoke all specified builders for copying form values to entity
// properties.
if (isset($form['#entity_builders'])) {
foreach ($form['#entity_builders'] as $function) {
call_user_func_array($function, array($entity->getEntityTypeId(), $entity, &$form, &$form_state));
}
}
return $entity;
}
/**
* Copies top-level form values to entity properties
*
* This should not change existing entity properties that are not being edited
* by this form.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity the current form should operate upon.
* @param array $form
* A nested array of form elements comprising the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*/
protected function copyFormValuesToEntity(EntityInterface $entity, array $form, FormStateInterface $form_state) {
$values = $form_state->getValues();
if ($this->entity instanceof EntityWithPluginCollectionInterface) {
// Do not manually update values represented by plugin collections.
$values = array_diff_key($values, $this->entity->getPluginCollections());
}
// @todo: This relies on a method that only exists for config and content
// entities, in a different way. Consider moving this logic to a config
// entity specific implementation.
foreach ($values as $key => $value) {
$entity->set($key, $value);
}
}
/**
* {@inheritdoc}
*/
public function getEntity() {
return $this->entity;
}
/**
* {@inheritdoc}
*/
public function setEntity(EntityInterface $entity) {
$this->entity = $entity;
return $this;
}
/**
* {@inheritdoc}
*/
public function getEntityFromRouteMatch(RouteMatchInterface $route_match, $entity_type_id) {
if ($route_match->getRawParameter($entity_type_id) !== NULL) {
$entity = $route_match->getParameter($entity_type_id);
}
else {
$entity = $this->entityManager->getStorage($entity_type_id)->create([]);
}
return $entity;
}
/**
* Prepares the entity object before the form is built first.
*/
protected function prepareEntity() {}
/**
* Invokes the specified prepare hook variant.
*
* @param string $hook
* The hook variant name.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*/
protected function prepareInvokeAll($hook, FormStateInterface $form_state) {
$implementations = $this->moduleHandler->getImplementations($hook);
foreach ($implementations as $module) {
$function = $module . '_' . $hook;
if (function_exists($function)) {
// Ensure we pass an updated translation object and form display at
// each invocation, since they depend on form state which is alterable.
$args = array($this->entity, $this->operation, &$form_state);
call_user_func_array($function, $args);
}
}
}
/**
* {@inheritdoc}
*/
public function getOperation() {
return $this->operation;
}
/**
* {@inheritdoc}
*/
public function setModuleHandler(ModuleHandlerInterface $module_handler) {
$this->moduleHandler = $module_handler;
return $this;
}
/**
* {@inheritdoc}
*/
public function setEntityManager(EntityManagerInterface $entity_manager) {
$this->entityManager = $entity_manager;
return $this;
}
}

View file

@ -0,0 +1,56 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\EntityFormBuilder.
*/
namespace Drupal\Core\Entity;
use Drupal\Core\Form\FormBuilderInterface;
use Drupal\Core\Form\FormState;
/**
* Builds entity forms.
*/
class EntityFormBuilder implements EntityFormBuilderInterface {
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* The form builder.
*
* @var \Drupal\Core\Form\FormBuilderInterface
*/
protected $formBuilder;
/**
* Constructs a new EntityFormBuilder.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
* @param \Drupal\Core\Form\FormBuilderInterface $form_builder
* The form builder.
*/
public function __construct(EntityManagerInterface $entity_manager, FormBuilderInterface $form_builder) {
$this->entityManager = $entity_manager;
$this->formBuilder = $form_builder;
}
/**
* {@inheritdoc}
*/
public function getForm(EntityInterface $entity, $operation = 'default', array $form_state_additions = array()) {
$form_object = $this->entityManager->getFormObject($entity->getEntityTypeId(), $operation);
$form_object->setEntity($entity);
$form_state = (new FormState())->setFormState($form_state_additions);
return $this->formBuilder->buildForm($form_object, $form_state);
}
}

View file

@ -0,0 +1,42 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\EntityFormBuilderInterface.
*/
namespace Drupal\Core\Entity;
/**
* Builds entity forms.
*/
interface EntityFormBuilderInterface {
/**
* Gets the built and processed entity form for the given entity.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity to be created or edited.
* @param string $operation
* (optional) The operation identifying the form variation to be returned.
* Defaults to 'default'. This is typically used in routing:
* @code
* _entity_form: node.book_outline
* @endcode
* where "book_outline" is the value of $operation.
* @param array $form_state_additions
* (optional) An associative array used to build the current state of the
* form. Use this to pass additional information to the form, such as the
* langcode. Defaults to an empty array.
*
* @code
* $form_state_additions['langcode'] = $langcode;
* $form = \Drupal::service('entity.form_builder')->getForm($entity, 'default', $form_state_additions);
* @endcode
*
* @return array
* The processed form for the given entity and operation.
*/
public function getForm(EntityInterface $entity, $operation = 'default', array $form_state_additions = array());
}

View file

@ -0,0 +1,156 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\EntityFormInterface.
*/
namespace Drupal\Core\Entity;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Form\BaseFormIdInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\StringTranslation\TranslationInterface;
/**
* Defines an interface for entity form classes.
*/
interface EntityFormInterface extends BaseFormIdInterface {
/**
* Sets the operation for this form.
*
* @param string $operation
* The name of the current operation.
*
* @return $this
*/
public function setOperation($operation);
/**
* Gets the operation identifying the form.
*
* @return string
* The name of the operation.
*/
public function getOperation();
/**
* Gets the form entity.
*
* The form entity which has been used for populating form element defaults.
*
* @return \Drupal\Core\Entity\EntityInterface
* The current form entity.
*/
public function getEntity();
/**
* Sets the form entity.
*
* Sets the form entity which will be used for populating form element
* defaults. Usually, the form entity gets updated by
* \Drupal\Core\Entity\EntityFormInterface::submit(), however this may
* be used to completely exchange the form entity, e.g. when preparing the
* rebuild of a multi-step form.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity the current form should operate upon.
*
* @return $this
*/
public function setEntity(EntityInterface $entity);
/**
* Determines which entity will be used by this form from a RouteMatch object.
*
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* The route match.
* @param string $entity_type_id
* The entity type identifier.
*
* @return \Drupal\Core\Entity\EntityInterface
* The entity object as determined from the passed-in route match.
*/
public function getEntityFromRouteMatch(RouteMatchInterface $route_match, $entity_type_id);
/**
* Builds an updated entity object based upon the submitted form values.
*
* For building the updated entity object the form's entity is cloned and
* the submitted form values are copied to entity properties. The form's
* entity remains unchanged.
*
* @see \Drupal\Core\Entity\EntityFormInterface::getEntity()
*
* @param array $form
* A nested array form elements comprising the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*
* @return \Drupal\Core\Entity\EntityInterface
* An updated copy of the form's entity object.
*/
public function buildEntity(array $form, FormStateInterface $form_state);
/**
* Validates the submitted form values of the entity form.
*
* @param array $form
* A nested array form elements comprising the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*
* @return \Drupal\Core\Entity\ContentEntityTypeInterface
* The built entity.
*/
public function validate(array $form, FormStateInterface $form_state);
/**
* Form submission handler for the 'save' action.
*
* Normally this method should be overridden to provide specific messages to
* the user and redirect the form after the entity has been saved.
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*
* @return int
* Either SAVED_NEW or SAVED_UPDATED, depending on the operation performed.
*/
public function save(array $form, FormStateInterface $form_state);
/**
* Sets the string translation service for this form.
*
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
* The translation manager.
*
* @return $this
*/
public function setStringTranslation(TranslationInterface $string_translation);
/**
* Sets the module handler for this form.
*
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
*
* @return $this
*/
public function setModuleHandler(ModuleHandlerInterface $module_handler);
/**
* Sets the entity manager for this form.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
*
* @return $this
*/
public function setEntityManager(EntityManagerInterface $entity_manager);
}

View file

@ -0,0 +1,15 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\EntityFormModeInterface.
*/
namespace Drupal\Core\Entity;
/**
* Provides an interface defining an entity form mode entity type.
*/
interface EntityFormModeInterface extends EntityDisplayModeInterface {
}

View file

@ -0,0 +1,56 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\EntityHandlerBase.
*/
namespace Drupal\Core\Entity;
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
/**
* Provides a base class for entity handlers.
*
* @todo Deprecate this in https://www.drupal.org/node/2471663.
*/
abstract class EntityHandlerBase {
use StringTranslationTrait;
use DependencySerializationTrait;
/**
* The module handler to invoke hooks on.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* Gets the module handler.
*
* @return \Drupal\Core\Extension\ModuleHandlerInterface
* The module handler.
*/
protected function moduleHandler() {
if (!$this->moduleHandler) {
$this->moduleHandler = \Drupal::moduleHandler();
}
return $this->moduleHandler;
}
/**
* Sets the module handler for this handler.
*
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
*
* @return $this
*/
public function setModuleHandler(ModuleHandlerInterface $module_handler) {
$this->moduleHandler = $module_handler;
return $this;
}
}

View file

@ -0,0 +1,38 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\EntityHandlerInterface.
*/
namespace Drupal\Core\Entity;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines an interface for entity handlers.
*
* This interface can be implemented by entity handlers that require
* dependency injection.
*/
interface EntityHandlerInterface {
/**
* Instantiates a new instance of this entity handler.
*
* This is a factory method that returns a new instance of this object. The
* factory should pass any needed dependencies into the constructor of this
* object, but not the container itself. Every call to this method must return
* a new instance of this object; that is, it may not implement a singleton.
*
* @param \Symfony\Component\DependencyInjection\ContainerInterface $container
* The service container this object should use.
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type definition.
*
* @return static
* A new instance of the entity handler.
*/
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type);
}

View file

@ -0,0 +1,419 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\EntityInterface.
*/
namespace Drupal\Core\Entity;
use Drupal\Core\Access\AccessibleInterface;
use Drupal\Core\Cache\CacheableDependencyInterface;
/**
* Defines a common interface for all entity objects.
*
* @ingroup entity_api
*/
interface EntityInterface extends AccessibleInterface, CacheableDependencyInterface {
/**
* Gets the entity UUID (Universally Unique Identifier).
*
* The UUID is guaranteed to be unique and can be used to identify an entity
* across multiple systems.
*
* @return string|null
* The UUID of the entity, or NULL if the entity does not have one.
*/
public function uuid();
/**
* Gets the identifier.
*
* @return string|int|null
* The entity identifier, or NULL if the object does not yet have an
* identifier.
*/
public function id();
/**
* Gets the language of the entity.
*
* @return \Drupal\Core\Language\LanguageInterface
* The language object.
*/
public function language();
/**
* Determines whether the entity is new.
*
* Usually an entity is new if no ID exists for it yet. However, entities may
* be enforced to be new with existing IDs too.
*
* @return bool
* TRUE if the entity is new, or FALSE if the entity has already been saved.
*
* @see \Drupal\Core\Entity\EntityInterface::enforceIsNew()
*/
public function isNew();
/**
* Enforces an entity to be new.
*
* Allows migrations to create entities with pre-defined IDs by forcing the
* entity to be new before saving.
*
* @param bool $value
* (optional) Whether the entity should be forced to be new. Defaults to
* TRUE.
*
* @return $this
*
* @see \Drupal\Core\Entity\EntityInterface::isNew()
*/
public function enforceIsNew($value = TRUE);
/**
* Gets the ID of the type of the entity.
*
* @return string
* The entity type ID.
*/
public function getEntityTypeId();
/**
* Gets the bundle of the entity.
*
* @return string
* The bundle of the entity. Defaults to the entity type ID if the entity
* type does not make use of different bundles.
*/
public function bundle();
/**
* Gets the label of the entity.
*
* @return string|null
* The label of the entity, or NULL if there is no label defined.
*/
public function label();
/**
* Gets the URI elements of the entity.
*
* URI templates might be set in the links array in an annotation, for
* example:
* @code
* links = {
* "canonical" = "/node/{node}",
* "edit-form" = "/node/{node}/edit",
* "version-history" = "/node/{node}/revisions"
* }
* @endcode
* or specified in a callback function set like:
* @code
* uri_callback = "comment_uri",
* @endcode
* If the path is not set in the links array, the uri_callback function is
* used for setting the path. If this does not exist and the link relationship
* type is canonical, the path is set using the default template:
* entity/entityType/id.
*
* @param string $rel
* The link relationship type, for example: canonical or edit-form.
* @param array $options
* See \Drupal\Core\Routing\UrlGeneratorInterface::generateFromRoute() for
* the available options.
*
* @return \Drupal\Core\Url
*/
public function urlInfo($rel = 'canonical', array $options = array());
/**
* Gets the public URL for this entity.
*
* @param string $rel
* The link relationship type, for example: canonical or edit-form.
* @param array $options
* See \Drupal\Core\Routing\UrlGeneratorInterface::generateFromRoute() for
* the available options.
*
* @return string
* The URL for this entity.
*/
public function url($rel = 'canonical', $options = array());
/**
* Generates the HTML for a link to this entity.
*
* @param string|null $text
* (optional) The link text for the anchor tag as a translated string.
* If NULL, it will use the entity's label. Defaults to NULL.
* @param string $rel
* (optional) The link relationship type. Defaults to 'canonical'.
* @param array $options
* See \Drupal\Core\Routing\UrlGeneratorInterface::generateFromRoute() for
* the available options.
*
* @return string
* An HTML string containing a link to the entity.
*/
public function link($text = NULL, $rel = 'canonical', array $options = []);
/**
* Indicates if a link template exists for a given key.
*
* @param string $key
* The link type.
*
* @return bool
* TRUE if the link template exists, FALSE otherwise.
*/
public function hasLinkTemplate($key);
/**
* Gets a list of URI relationships supported by this entity.
*
* @return string[]
* An array of link relationships supported by this entity.
*/
public function uriRelationships();
/**
* Loads an entity.
*
* @param mixed $id
* The id of the entity to load.
*
* @return static
* The entity object or NULL if there is no entity with the given ID.
*/
public static function load($id);
/**
* Loads one or more entities.
*
* @param array $ids
* An array of entity IDs, or NULL to load all entities.
*
* @return static[]
* An array of entity objects indexed by their IDs.
*/
public static function loadMultiple(array $ids = NULL);
/**
* Constructs a new entity object, without permanently saving it.
*
* @param array $values
* (optional) An array of values to set, keyed by property name. If the
* entity type has bundles, the bundle key has to be specified.
*
* @return static
* The entity object.
*/
public static function create(array $values = array());
/**
* Saves an entity permanently.
*
* When saving existing entities, the entity is assumed to be complete,
* partial updates of entities are not supported.
*
* @return int
* Either SAVED_NEW or SAVED_UPDATED, depending on the operation performed.
*
* @throws \Drupal\Core\Entity\EntityStorageException
* In case of failures an exception is thrown.
*/
public function save();
/**
* Deletes an entity permanently.
*
* @throws \Drupal\Core\Entity\EntityStorageException
* In case of failures an exception is thrown.
*/
public function delete();
/**
* Acts on an entity before the presave hook is invoked.
*
* Used before the entity is saved and before invoking the presave hook.
*
* @param \Drupal\Core\Entity\EntityStorageInterface $storage
* The entity storage object.
*/
public function preSave(EntityStorageInterface $storage);
/**
* 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.
*
* @param \Drupal\Core\Entity\EntityStorageInterface $storage
* The entity storage object.
* @param bool $update
* TRUE if the entity has been updated, or FALSE if it has been inserted.
*/
public function postSave(EntityStorageInterface $storage, $update = TRUE);
/**
* Changes the values of an entity before it is created.
*
* Load defaults for example.
*
* @param \Drupal\Core\Entity\EntityStorageInterface $storage
* The entity storage object.
* @param mixed[] $values
* An array of values to set, keyed by property name. If the entity type has
* bundles the bundle key has to be specified.
*/
public static function preCreate(EntityStorageInterface $storage, array &$values);
/**
* Acts on an entity after it is created but before hooks are invoked.
*
* @param \Drupal\Core\Entity\EntityStorageInterface $storage
* The entity storage object.
*/
public function postCreate(EntityStorageInterface $storage);
/**
* Acts on entities before they are deleted and before hooks are invoked.
*
* Used before the entities are deleted and before invoking the delete hook.
*
* @param \Drupal\Core\Entity\EntityStorageInterface $storage
* The entity storage object.
* @param \Drupal\Core\Entity\EntityInterface[] $entities
* An array of entities.
*/
public static function preDelete(EntityStorageInterface $storage, array $entities);
/**
* Acts on deleted entities before the delete hook is invoked.
*
* Used after the entities are deleted but before invoking the delete hook.
*
* @param \Drupal\Core\Entity\EntityStorageInterface $storage
* The entity storage object.
* @param \Drupal\Core\Entity\EntityInterface[] $entities
* An array of entities.
*/
public static function postDelete(EntityStorageInterface $storage, array $entities);
/**
* Acts on loaded entities.
*
* @param \Drupal\Core\Entity\EntityStorageInterface $storage
* The entity storage object.
* @param \Drupal\Core\Entity\EntityInterface[] $entities
* An array of entities.
*/
public static function postLoad(EntityStorageInterface $storage, array &$entities);
/**
* Creates a duplicate of the entity.
*
* @return static
* A clone of $this with all identifiers unset, so saving it inserts a new
* entity into the storage system.
*/
public function createDuplicate();
/**
* Gets the entity type definition.
*
* @return \Drupal\Core\Entity\EntityTypeInterface
* The entity type definition.
*/
public function getEntityType();
/**
* Gets a list of entities referenced by this entity.
*
* @return \Drupal\Core\Entity\EntityInterface[]
* An array of entities.
*/
public function referencedEntities();
/**
* Gets the original ID.
*
* @return int|string|null
* The original ID, or NULL if no ID was set or for entity types that do not
* support renames.
*/
public function getOriginalId();
/**
* Sets the original ID.
*
* @param int|string|null $id
* The new ID to set as original ID. If the entity supports renames, setting
* NULL will prevent an update from being considered a rename.
*
* @return $this
*/
public function setOriginalId($id);
/**
* Gets an array of all property values.
*
* @return mixed[]
* An array of property values, keyed by property name.
*/
public function toArray();
/**
* Gets a typed data object for this entity object.
*
* The returned typed data object wraps this entity and allows dealing with
* entities based on the generic typed data API.
*
* @return \Drupal\Core\TypedData\ComplexDataInterface
* The typed data object for this entity.
*
* @see \Drupal\Core\TypedData\TypedDataInterface
*/
public function getTypedData();
/**
* Gets the key that is used to store configuration dependencies.
*
* @return string
* The key to be used in configuration dependencies when storing
* dependencies on entities of this type.
*
* @see \Drupal\Core\Entity\EntityTypeInterface::getConfigDependencyKey()
*/
public function getConfigDependencyKey();
/**
* Gets the configuration dependency name.
*
* Configuration entities can depend on content and configuration entities.
* They store an array of content and config dependency names in their
* "dependencies" key.
*
* @return string
* The configuration dependency name.
*
* @see \Drupal\Core\Config\Entity\ConfigDependencyManager
*/
public function getConfigDependencyName();
/**
* Gets the configuration target identifier for the entity.
*
* Used to supply the correct format for storing a reference targeting this
* entity in configuration.
*
* @return string
* The configuration target identifier.
*/
public function getConfigTarget();
}

View file

@ -0,0 +1,246 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\EntityListBuilder.
*/
namespace Drupal\Core\Entity;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Component\Utility\SafeMarkup;
/**
* Defines a generic implementation to build a listing of entities.
*
* @ingroup entity_api
*/
class EntityListBuilder extends EntityHandlerBase implements EntityListBuilderInterface, EntityHandlerInterface {
/**
* The entity storage class.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $storage;
/**
* The entity type ID.
*
* @var string
*/
protected $entityTypeId;
/**
* Information about the entity type.
*
* @var \Drupal\Core\Entity\EntityTypeInterface
*/
protected $entityType;
/**
* The number of entities to list per page.
*
* @var int
*/
protected $limit = 50;
/**
* {@inheritdoc}
*/
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
return new static(
$entity_type,
$container->get('entity.manager')->getStorage($entity_type->id())
);
}
/**
* Constructs a new EntityListBuilder object.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type definition.
* @param \Drupal\Core\Entity\EntityStorageInterface $storage
* The entity storage class.
*/
public function __construct(EntityTypeInterface $entity_type, EntityStorageInterface $storage) {
$this->entityTypeId = $entity_type->id();
$this->storage = $storage;
$this->entityType = $entity_type;
}
/**
* {@inheritdoc}
*/
public function getStorage() {
return $this->storage;
}
/**
* {@inheritdoc}
*/
public function load() {
$entity_ids = $this->getEntityIds();
return $this->storage->loadMultiple($entity_ids);
}
/**
* Loads entity IDs using a pager sorted by the entity id.
*
* @return array
* An array of entity IDs.
*/
protected function getEntityIds() {
$query = $this->getStorage()->getQuery();
$keys = $this->entityType->getKeys();
return $query
->sort($keys['id'])
->pager($this->limit)
->execute();
}
/**
* Gets the escaped label of an entity.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity being listed.
*
* @return string
* The escaped entity label.
*/
protected function getLabel(EntityInterface $entity) {
return SafeMarkup::checkPlain($entity->label());
}
/**
* {@inheritdoc}
*/
public function getOperations(EntityInterface $entity) {
$operations = $this->getDefaultOperations($entity);
$operations += $this->moduleHandler()->invokeAll('entity_operation', array($entity));
$this->moduleHandler->alter('entity_operation', $operations, $entity);
uasort($operations, '\Drupal\Component\Utility\SortArray::sortByWeightElement');
return $operations;
}
/**
* Gets this list's default operations.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity the operations are for.
*
* @return array
* The array structure is identical to the return value of
* self::getOperations().
*/
protected function getDefaultOperations(EntityInterface $entity) {
$operations = array();
if ($entity->access('update') && $entity->hasLinkTemplate('edit-form')) {
$operations['edit'] = array(
'title' => $this->t('Edit'),
'weight' => 10,
'url' => $entity->urlInfo('edit-form'),
);
}
if ($entity->access('delete') && $entity->hasLinkTemplate('delete-form')) {
$operations['delete'] = array(
'title' => $this->t('Delete'),
'weight' => 100,
'url' => $entity->urlInfo('delete-form'),
);
}
return $operations;
}
/**
* Builds the header row for the entity listing.
*
* @return array
* A render array structure of header strings.
*
* @see \Drupal\Core\Entity\EntityListBuilder::render()
*/
public function buildHeader() {
$row['operations'] = $this->t('Operations');
return $row;
}
/**
* Builds a row for an entity in the entity listing.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity for this row of the list.
*
* @return array
* A render array structure of fields for this entity.
*
* @see \Drupal\Core\Entity\EntityListBuilder::render()
*/
public function buildRow(EntityInterface $entity) {
$row['operations']['data'] = $this->buildOperations($entity);
return $row;
}
/**
* Builds a renderable list of operation links for the entity.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity on which the linked operations will be performed.
*
* @return array
* A renderable array of operation links.
*
* @see \Drupal\Core\Entity\EntityListBuilder::buildRow()
*/
public function buildOperations(EntityInterface $entity) {
$build = array(
'#type' => 'operations',
'#links' => $this->getOperations($entity),
);
return $build;
}
/**
* {@inheritdoc}
*
* Builds the entity listing as renderable array for table.html.twig.
*
* @todo Add a link to add a new item to the #empty text.
*/
public function render() {
$build['table'] = array(
'#type' => 'table',
'#header' => $this->buildHeader(),
'#title' => $this->getTitle(),
'#rows' => array(),
'#empty' => $this->t('There is no @label yet.', array('@label' => $this->entityType->getLabel())),
'#cache' => [
'contexts' => $this->entityType->getListCacheContexts(),
],
);
foreach ($this->load() as $entity) {
if ($row = $this->buildRow($entity)) {
$build['table']['#rows'][$entity->id()] = $row;
}
}
$build['pager'] = array(
'#type' => 'pager',
);
return $build;
}
/**
* Gets the title of the page.
*
* @return string
* A string title of the page.
*/
protected function getTitle() {
return;
}
}

View file

@ -0,0 +1,58 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\EntityListBuilderInterface.
*/
namespace Drupal\Core\Entity;
/**
* Defines an interface to build entity listings.
*/
interface EntityListBuilderInterface {
/**
* Gets the entity storage.
*
* @return \Drupal\Core\Entity\EntityStorageInterface
* The storage used by this list builder.
*/
public function getStorage();
/**
* Loads entities of this type from storage for listing.
*
* This allows the implementation to manipulate the listing, like filtering or
* sorting the loaded entities.
*
* @return \Drupal\Core\Entity\EntityInterface[]
* An array of entities implementing \Drupal\Core\Entity\EntityInterface.
*/
public function load();
/**
* Provides an array of information to build a list of operation links.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity the operations are for.
*
* @return array
* An associative array of operation link data for this list, keyed by
* operation name, containing the following key-value pairs:
* - title: The localized title of the operation.
* - href: The path for the operation.
* - options: An array of URL options for the path.
* - weight: The weight of this operation.
*/
public function getOperations(EntityInterface $entity);
/**
* Builds a listing of entities for the given entity type.
*
* @return array
* A render array as expected by drupal_render().
*/
public function render();
}

View file

@ -0,0 +1,13 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\EntityMalformedException.
*/
namespace Drupal\Core\Entity;
/**
* Defines an exception thrown when a malformed entity is passed.
*/
class EntityMalformedException extends \Exception { }

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,518 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\EntityManagerInterface.
*/
namespace Drupal\Core\Entity;
use Drupal\Component\Plugin\Discovery\CachedDiscoveryInterface;
use Drupal\Component\Plugin\PluginManagerInterface;
use Drupal\Core\Field\FieldDefinitionListenerInterface;
use Drupal\Core\Field\FieldStorageDefinitionListenerInterface;
/**
* Provides an interface for entity type managers.
*/
interface EntityManagerInterface extends PluginManagerInterface, EntityTypeListenerInterface, EntityBundleListenerInterface, FieldStorageDefinitionListenerInterface, FieldDefinitionListenerInterface, CachedDiscoveryInterface {
/**
* Builds a list of entity type labels suitable for a Form API options list.
*
* @param bool $group
* (optional) Whether to group entity types by plugin group (e.g. 'content',
* 'config'). Defaults to FALSE.
*
* @return array
* An array of entity type labels, keyed by entity type name.
*/
public function getEntityTypeLabels($group = FALSE);
/**
* Gets the base field definitions for a content entity type.
*
* Only fields that are not specific to a given bundle or set of bundles are
* returned. This excludes configurable fields, as they are always attached
* to a specific bundle.
*
* @param string $entity_type_id
* The entity type ID. Only entity types that implement
* \Drupal\Core\Entity\FieldableEntityInterface are supported.
*
* @return \Drupal\Core\Field\FieldDefinitionInterface[]
* The array of base field definitions for the entity type, keyed by field
* name.
*
* @throws \LogicException
* Thrown if one of the entity keys is flagged as translatable.
*/
public function getBaseFieldDefinitions($entity_type_id);
/**
* Gets the field definitions for a specific bundle.
*
* @param string $entity_type_id
* The entity type ID. Only entity types that implement
* \Drupal\Core\Entity\FieldableEntityInterface are supported.
* @param string $bundle
* The bundle.
*
* @return \Drupal\Core\Field\FieldDefinitionInterface[]
* The array of field definitions for the bundle, keyed by field name.
*/
public function getFieldDefinitions($entity_type_id, $bundle);
/**
* Gets the field storage definitions for a content entity type.
*
* This returns all field storage definitions for base fields and bundle
* fields of an entity type. Note that field storage definitions of a base
* field equal the full base field definition (i.e. they implement
* FieldDefinitionInterface), while the storage definitions for bundle fields
* may implement FieldStorageDefinitionInterface only.
*
* @param string $entity_type_id
* The entity type ID. Only content entities are supported.
*
* @return \Drupal\Core\Field\FieldStorageDefinitionInterface[]
* The array of field storage definitions for the entity type, keyed by
* field name.
*
* @see \Drupal\Core\Field\FieldStorageDefinitionInterface
*/
public function getFieldStorageDefinitions($entity_type_id);
/**
* Gets the entity type's most recently installed field storage definitions.
*
* During the application lifetime, field storage definitions can change. For
* example, updated code can be deployed. The getFieldStorageDefinitions()
* method will always return the definitions as determined by the current
* codebase. This method, however, returns what the definitions were when the
* last time that one of the
* \Drupal\Core\Field\FieldStorageDefinitionListenerInterface events was last
* fired and completed successfully. In other words, the definitions that
* the entity type's handlers have incorporated into the application state.
* For example, if the entity type's storage handler is SQL-based, the
* definitions for which database tables were created.
*
* Application management code can check if getFieldStorageDefinitions()
* differs from getLastInstalledFieldStorageDefinitions() and decide whether
* to:
* - Invoke the appropriate
* \Drupal\Core\Field\FieldStorageDefinitionListenerInterface
* events so that handlers react to the new definitions.
* - Raise a warning that the application state is incompatible with the
* codebase.
* - Perform some other action.
*
* @param string $entity_type_id
* The entity type ID.
*
* @return \Drupal\Core\Field\FieldStorageDefinitionInterface[]
* The array of installed field storage definitions for the entity type,
* keyed by field name.
*
* @see \Drupal\Core\Entity\EntityTypeListenerInterface
*/
public function getLastInstalledFieldStorageDefinitions($entity_type_id);
/**
* Gets a lightweight map of fields across bundles.
*
* @return array
* An array keyed by entity type. Each value is an array which keys are
* field names and value is an array with two entries:
* - type: The field type.
* - bundles: The bundles in which the field appears.
*/
public function getFieldMap();
/**
* Gets a lightweight map of fields across bundles filtered by field type.
*
* @param string $field_type
* The field type to filter by.
*
* @return array
* An array keyed by entity type. Each value is an array which keys are
* field names and value is an array with two entries:
* - type: The field type.
* - bundles: The bundles in which the field appears.
*/
public function getFieldMapByFieldType($field_type);
/**
* Creates a new access control handler instance.
*
* @param string $entity_type
* The entity type for this access control handler.
*
* @return \Drupal\Core\Entity\EntityAccessControlHandlerInterface.
* A access control handler instance.
*/
public function getAccessControlHandler($entity_type);
/**
* Creates a new storage instance.
*
* @param string $entity_type
* The entity type for this storage.
*
* @return \Drupal\Core\Entity\EntityStorageInterface
* A storage instance.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
*/
public function getStorage($entity_type);
/**
* Get the bundle info of all entity types.
*
* @return array
* An array of all bundle information.
*/
public function getAllBundleInfo();
/**
* {@inheritdoc}
*/
public function clearCachedDefinitions();
/**
* Clears static and persistent field definition caches.
*/
public function clearCachedFieldDefinitions();
/**
* Clears static and persistent bundles.
*/
public function clearCachedBundles();
/**
* Creates a new view builder instance.
*
* @param string $entity_type
* The entity type for this view builder.
*
* @return \Drupal\Core\Entity\EntityViewBuilderInterface.
* A view builder instance.
*/
public function getViewBuilder($entity_type);
/**
* Creates a new entity list builder.
*
* @param string $entity_type
* The entity type for this list builder.
*
* @return \Drupal\Core\Entity\EntityListBuilderInterface
* An entity list builder instance.
*/
public function getListBuilder($entity_type);
/**
* Creates a new form instance.
*
* @param string $entity_type
* The entity type for this form.
* @param string $operation
* The name of the operation to use, e.g., 'default'.
*
* @return \Drupal\Core\Entity\EntityFormInterface
* A form instance.
*/
public function getFormObject($entity_type, $operation);
/**
* Gets all route provider instances.
*
* @param string $entity_type
* The entity type for this route providers.
*
* @return \Drupal\Core\Entity\Routing\EntityRouteProviderInterface[]
*/
public function getRouteProviders($entity_type);
/**
* Checks whether a certain entity type has a certain handler.
*
* @param string $entity_type
* The name of the entity type.
* @param string $handler_type
* The name of the handler.
*
* @return bool
* Returns TRUE if the entity type has the handler, else FALSE.
*/
public function hasHandler($entity_type, $handler_type);
/**
* Creates a new handler instance for a entity type and handler type.
*
* @param string $entity_type
* The entity type for this controller.
* @param string $handler_type
* The controller type to create an instance for.
*
* @return object
* A handler instance.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
*/
public function getHandler($entity_type, $handler_type);
/**
* Creates new handler instance.
*
* Usually \Drupal\Core\Entity\EntityManagerInterface::getHandler() is
* preferred since that method has additional checking that the class exists
* and has static caches.
*
* @param mixed $class
* The handler class to instantiate.
* @param \Drupal\Core\Entity\EntityTypeInterface $definition
* The entity type definition.
*
* @return object
* A handler instance.
*/
public function createHandlerInstance($class, EntityTypeInterface $definition = null);
/**
* Gets the bundle info of an entity type.
*
* @param string $entity_type
* The entity type.
*
* @return array
* Returns the bundle information for the specified entity type.
*/
public function getBundleInfo($entity_type);
/**
* Gets the "extra fields" for a bundle.
*
* @param string $entity_type_id
* The entity type ID.
* @param string $bundle
* The bundle name.
*
* @return array
* A nested array of 'pseudo-field' elements. Each list is nested within the
* following keys: entity type, bundle name, context (either 'form' or
* 'display'). The keys are the name of the elements as appearing in the
* renderable array (either the entity form or the displayed entity). The
* value is an associative array:
* - label: The human readable name of the element. Make sure you sanitize
* this appropriately.
* - description: A short description of the element contents.
* - weight: The default weight of the element.
* - visible: (optional) The default visibility of the element. Defaults to
* TRUE.
* - edit: (optional) String containing markup (normally a link) used as the
* element's 'edit' operation in the administration interface. Only for
* 'form' context.
* - delete: (optional) String containing markup (normally a link) used as the
* element's 'delete' operation in the administration interface. Only for
* 'form' context.
*/
public function getExtraFields($entity_type_id, $bundle);
/**
* Gets the entity translation to be used in the given context.
*
* This will check whether a translation for the desired language is available
* and if not, it will fall back to the most appropriate translation based on
* the provided context.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity whose translation will be returned.
* @param string $langcode
* (optional) The language of the current context. Defaults to the current
* content language.
* @param array $context
* (optional) An associative array of arbitrary data that can be useful to
* determine the proper fallback sequence.
*
* @return \Drupal\Core\Entity\EntityInterface
* An entity object for the translated data.
*
* @see \Drupal\Core\Language\LanguageManagerInterface::getFallbackCandidates()
*/
public function getTranslationFromContext(EntityInterface $entity, $langcode = NULL, $context = array());
/**
* {@inheritdoc}
*
* @return \Drupal\Core\Entity\EntityTypeInterface|null
*/
public function getDefinition($entity_type_id, $exception_on_invalid = TRUE);
/**
* Gets the entity type definition in its most recently installed state.
*
* During the application lifetime, entity type definitions can change. For
* example, updated code can be deployed. The getDefinition() method will
* always return the definition as determined by the current codebase. This
* method, however, returns what the definition was when the last time that
* one of the \Drupal\Core\Entity\EntityTypeListenerInterface events was last
* fired and completed successfully. In other words, the definition that
* the entity type's handlers have incorporated into the application state.
* For example, if the entity type's storage handler is SQL-based, the
* definition for which database tables were created.
*
* Application management code can check if getDefinition() differs from
* getLastInstalledDefinition() and decide whether to:
* - Invoke the appropriate \Drupal\Core\Entity\EntityTypeListenerInterface
* event so that handlers react to the new definition.
* - Raise a warning that the application state is incompatible with the
* codebase.
* - Perform some other action.
*
* @param string $entity_type_id
* The entity type ID.
*
* @return \Drupal\Core\Entity\EntityTypeInterface|null
* The installed entity type definition, or NULL if the entity type has
* not yet been installed via onEntityTypeCreate().
*
* @see \Drupal\Core\Entity\EntityTypeListenerInterface
*/
public function getLastInstalledDefinition($entity_type_id);
/**
* {@inheritdoc}
*
* @return \Drupal\Core\Entity\EntityTypeInterface[]
*/
public function getDefinitions();
/**
* Gets the entity view mode info for all entity types.
*
* @return array
* The view mode info for all entity types.
*/
public function getAllViewModes();
/**
* Gets the entity view mode info for a specific entity type.
*
* @param string $entity_type_id
* The entity type whose view mode info should be returned.
*
* @return array
* The view mode info for a specific entity type.
*/
public function getViewModes($entity_type_id);
/**
* Gets the entity form mode info for all entity types.
*
* @return array
* The form mode info for all entity types.
*/
public function getAllFormModes();
/**
* Gets the entity form mode info for a specific entity type.
*
* @param string $entity_type_id
* The entity type whose form mode info should be returned.
*
* @return array
* The form mode info for a specific entity type.
*/
public function getFormModes($entity_type_id);
/**
* Gets an array of view mode options.
*
* @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);
/**
* 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);
/**
* Loads an entity by UUID.
*
* Note that some entity types may not support UUIDs.
*
* @param string $entity_type_id
* The entity type ID to load from.
* @param string $uuid
* The UUID of the entity to load.
*
* @return \Drupal\Core\Entity\EntityInterface|null
* The entity object, or NULL if there is no entity with the given UUID.
*
* @throws \Drupal\Core\Entity\EntityStorageException
* Thrown in case the requested entity type does not support UUIDs.
*/
public function loadEntityByUuid($entity_type_id, $uuid);
/**
* Loads an entity by the config target identifier.
*
* @param string $entity_type_id
* The entity type ID to load from.
* @param string $target
* The configuration target to load, as returned from
* \Drupal\Core\Entity\EntityInterface::getConfigTarget().
*
* @return \Drupal\Core\Entity\EntityInterface|null
* The entity object, or NULL if there is no entity with the given config
* target identifier.
*
* @throws \Drupal\Core\Entity\EntityStorageException
* Thrown if the target identifier is a UUID but the entity type does not
* support UUIDs.
*
* @see \Drupal\Core\Entity\EntityInterface::getConfigTarget()
*/
public function loadEntityByConfigTarget($entity_type_id, $target);
/**
* Gets the entity type ID based on the class that is called on.
*
* Compares the class this is called on against the known entity classes
* and returns the entity type ID of a direct match or a subclass as fallback,
* to support entity type definitions that were altered.
*
* @param string $class_name
* Class name to use for searching the entity type ID.
*
* @return string
* The entity type ID.
*
* @throws \Drupal\Core\Entity\Exception\AmbiguousEntityClassException
* Thrown when multiple subclasses correspond to the called class.
* @throws \Drupal\Core\Entity\Exception\NoCorrespondingEntityClassException
* Thrown when no entity class corresponds to the called class.
*
* @see \Drupal\Core\Entity\Entity::load()
* @see \Drupal\Core\Entity\Entity::loadMultiple()
*/
public function getEntityTypeFromClass($class_name);
}

View file

@ -0,0 +1,81 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\EntityReferenceSelection\SelectionInterface.
*/
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 {
/**
* Gets the list of referenceable entities.
*
* @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),
* keyed by the entity ID.
*/
public function getReferenceableEntities($match = NULL, $match_operator = 'CONTAINS', $limit = 0);
/**
* Counts entities that are referenceable by a given field.
*
* @return int
* The number of referenceable entities.
*/
public function countReferenceableEntities($match = NULL, $match_operator = 'CONTAINS');
/**
* Validates that entities can be referenced by this field.
*
* @return array
* An array of valid entity IDs.
*/
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.
*
* @param \Drupal\Core\Database\Query\SelectInterface $query
* A Select Query object.
*/
public function entityQueryAlter(SelectInterface $query);
}

View file

@ -0,0 +1,118 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\EntityReferenceSelection\SelectionPluginManager.
*/
namespace Drupal\Core\Entity\EntityReferenceSelection;
use Drupal\Component\Plugin\FallbackPluginManagerInterface;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Plugin\DefaultPluginManager;
/**
* Plugin type manager for Entity Reference Selection plugins.
*
* @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 {
/**
* {@inheritdoc}
*/
public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
$this->alterInfo('entity_reference_selection');
$this->setCacheBackend($cache_backend, 'entity_reference_selection_plugins');
parent::__construct('Plugin/EntityReferenceSelection', $namespaces, $module_handler, 'Drupal\Core\Entity\EntityReferenceSelection\SelectionInterface', 'Drupal\Core\Entity\Annotation\EntityReferenceSelection');
}
/**
* {@inheritdoc}
*/
public function getInstance(array $options) {
if (!isset($options['target_type'])) {
throw new \InvalidArgumentException("Missing required 'target_type' property for a EntityReferenceSelection plugin.");
}
// Initialize default options.
$options += array(
'handler' => $this->getPluginId($options['target_type'], 'default'),
'handler_settings' => array(),
);
// A specific selection plugin ID was already specified.
if (strpos($options['handler'], ':') !== FALSE) {
$plugin_id = $options['handler'];
}
// Only a selection group name was specified.
else {
$plugin_id = $this->getPluginId($options['target_type'], $options['handler']);
}
return $this->createInstance($plugin_id, $options);
}
/**
* {@inheritdoc}
*/
public function getPluginId($target_type, $base_plugin_id) {
// Get all available selection plugins for this entity type.
$selection_handler_groups = $this->getSelectionGroups($target_type);
// Sort the selection plugins by weight and select the best match.
uasort($selection_handler_groups[$base_plugin_id], array('Drupal\Component\Utility\SortArray', 'sortByWeightElement'));
end($selection_handler_groups[$base_plugin_id]);
$plugin_id = key($selection_handler_groups[$base_plugin_id]);
return $plugin_id;
}
/**
* {@inheritdoc}
*/
public function getSelectionGroups($entity_type_id) {
$plugins = array();
$definitions = $this->getDefinitions();
// Do not display the 'broken' plugin in the UI.
unset($definitions['broken']);
foreach ($definitions as $plugin_id => $plugin) {
if (empty($plugin['entity_types']) || in_array($entity_type_id, $plugin['entity_types'])) {
$plugins[$plugin['group']][$plugin_id] = $plugin;
}
}
return $plugins;
}
/**
* {@inheritdoc}
*/
public function getSelectionHandler(FieldDefinitionInterface $field_definition, EntityInterface $entity = NULL) {
$options = array(
'target_type' => $field_definition->getFieldStorageDefinition()->getSetting('target_type'),
'handler' => $field_definition->getSetting('handler'),
'handler_settings' => $field_definition->getSetting('handler_settings') ?: array(),
'entity' => $entity,
);
return $this->getInstance($options);
}
/**
* {@inheritdoc}
*/
public function getFallbackPluginId($plugin_id, array $configuration = array()) {
return 'broken';
}
}

View file

@ -0,0 +1,56 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\EntityReferenceSelection\SelectionPluginManagerInterface.
*/
namespace Drupal\Core\Entity\EntityReferenceSelection;
use Drupal\Component\Plugin\PluginManagerInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
/**
* Defines an interface for the entity reference selection plugin manager.
*/
interface SelectionPluginManagerInterface extends PluginManagerInterface {
/**
* Gets the plugin ID for a given target entity type and base plugin ID.
*
* @param string $target_type
* The target entity type.
* @param string $base_plugin_id
* The base plugin ID (e.g. 'default' or 'views').
*
* @return string
* The plugin ID.
*/
public function getPluginId($target_type, $base_plugin_id);
/**
* Gets the selection plugins that can reference a specific entity type.
*
* @param string $entity_type_id
* A Drupal entity type ID.
*
* @return array
* An array of selection plugins grouped by selection group.
*/
public function getSelectionGroups($entity_type_id);
/**
* Gets the selection handler for a given entity_reference field.
*
* @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
* The field definition for the operation.
* @param \Drupal\Core\Entity\EntityInterface $entity
* (optional) The entity for the operation. Defaults to NULL.
*
* @return \Drupal\Core\Entity\EntityReferenceSelection\SelectionInterface
* The selection plugin.
*/
public function getSelectionHandler(FieldDefinitionInterface $field_definition, EntityInterface $entity = NULL);
}

View file

@ -0,0 +1,231 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\EntityResolverManager.
*/
namespace Drupal\Core\Entity;
use Drupal\Core\DependencyInjection\ClassResolverInterface;
use Symfony\Component\Routing\Route;
/**
* Sets the entity route parameter converter options automatically.
*
* If controllers of routes with route parameters, type-hint the parameters with
* an entity interface, upcasting is done automatically.
*/
class EntityResolverManager {
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* The class resolver.
*
* @var \Drupal\Core\DependencyInjection\ClassResolverInterface
*/
protected $classResolver;
/**
* Constructs a new EntityRouteAlterSubscriber.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
* @param \Drupal\Core\DependencyInjection\ClassResolverInterface $class_resolver
* The class resolver.
*/
public function __construct(EntityManagerInterface $entity_manager, ClassResolverInterface $class_resolver) {
$this->entityManager = $entity_manager;
$this->classResolver = $class_resolver;
}
/**
* Gets the controller class using route defaults.
*
* By design we cannot support all possible routes, but just the ones which
* use the defaults provided by core, which are _controller and _form.
*
* Rather than creating an instance of every controller determine the class
* and method that would be used. This is not possible for the service:method
* notation as the runtime container does not allow static introspection.
*
* @see \Drupal\Core\Controller\ControllerResolver::getControllerFromDefinition()
* @see \Drupal\Core\Controller\ClassResolver::getInstanceFromDefinition()
*
* @param array $defaults
* The default values provided by the route.
*
* @return string|null
* Returns the controller class, otherwise NULL.
*/
protected function getControllerClass(array $defaults) {
$controller = NULL;
if (isset($defaults['_controller'])) {
$controller = $defaults['_controller'];
}
if (isset($defaults['_form'])) {
$controller = $defaults['_form'];
// Check if the class exists and if so use the buildForm() method from the
// interface.
if (class_exists($controller)) {
return array($controller, 'buildForm');
}
}
if (strpos($controller, ':') === FALSE) {
if (method_exists($controller, '__invoke')) {
return array($controller, '__invoke');
}
if (function_exists($controller)) {
return $controller;
}
return NULL;
}
$count = substr_count($controller, ':');
if ($count == 1) {
// Controller in the service:method notation. Get the information from the
// service. This is dangerous as the controller could depend on services
// that could not exist at this point. There is however no other way to
// do it, as the container does not allow static introspection.
list($class_or_service, $method) = explode(':', $controller, 2);
return array($this->classResolver->getInstanceFromDefinition($class_or_service), $method);
}
elseif (strpos($controller, '::') !== FALSE) {
// Controller in the class::method notation.
return explode('::', $controller, 2);
}
return NULL;
}
/**
* Sets the upcasting information using reflection.
*
* @param string|array $controller
* A PHP callable representing the controller.
* @param \Symfony\Component\Routing\Route $route
* The route object to populate without upcasting information.
*
* @return bool
* Returns TRUE if the upcasting parameters could be set, FALSE otherwise.
*/
protected function setParametersFromReflection($controller, Route $route) {
$entity_types = $this->getEntityTypes();
$parameter_definitions = $route->getOption('parameters') ?: array();
$result = FALSE;
if (is_array($controller)) {
list($instance, $method) = $controller;
$reflection = new \ReflectionMethod($instance, $method);
}
else {
$reflection = new \ReflectionFunction($controller);
}
$parameters = $reflection->getParameters();
foreach ($parameters as $parameter) {
$parameter_name = $parameter->getName();
// If the parameter name matches with an entity type try to set the
// upcasting information automatically. Therefore take into account that
// the user has specified some interface, so the upcasting is intended.
if (isset($entity_types[$parameter_name])) {
$entity_type = $entity_types[$parameter_name];
$entity_class = $entity_type->getClass();
if (($reflection_class = $parameter->getClass()) && (is_subclass_of($entity_class, $reflection_class->name) || $entity_class == $reflection_class->name)) {
$parameter_definitions += array($parameter_name => array());
$parameter_definitions[$parameter_name] += array(
'type' => 'entity:' . $parameter_name,
);
$result = TRUE;
}
}
}
if (!empty($parameter_definitions)) {
$route->setOption('parameters', $parameter_definitions);
}
return $result;
}
/**
* Sets the upcasting information using the _entity_* route defaults.
*
* Supports the '_entity_view' and '_entity_form' route defaults.
*
* @param \Symfony\Component\Routing\Route $route
* The route object.
*/
protected function setParametersFromEntityInformation(Route $route) {
if ($entity_view = $route->getDefault('_entity_view')) {
list($entity_type) = explode('.', $entity_view, 2);
}
elseif ($entity_form = $route->getDefault('_entity_form')) {
list($entity_type) = explode('.', $entity_form, 2);
}
if (isset($entity_type) && isset($this->getEntityTypes()[$entity_type])) {
$parameter_definitions = $route->getOption('parameters') ?: array();
// First try to figure out whether there is already a parameter upcasting
// the same entity type already.
foreach ($parameter_definitions as $info) {
if (isset($info['type'])) {
// The parameter types are in the form 'entity:$entity_type'.
list(, $parameter_entity_type) = explode(':', $info['type'], 2);
if ($parameter_entity_type == $entity_type) {
return;
}
}
}
if (!isset($parameter_definitions[$entity_type])) {
$parameter_definitions[$entity_type] = array();
}
$parameter_definitions[$entity_type] += array(
'type' => 'entity:' . $entity_type,
);
if (!empty($parameter_definitions)) {
$route->setOption('parameters', $parameter_definitions);
}
}
}
/**
* Set the upcasting route objects.
*
* @param \Symfony\Component\Routing\Route $route
* The route object to add the upcasting information onto.
*/
public function setRouteOptions(Route $route) {
if ($controller = $this->getControllerClass($route->getDefaults())) {
// Try to use reflection.
if ($this->setParametersFromReflection($controller, $route)) {
return;
}
}
// Try to use _entity_* information on the route.
$this->setParametersFromEntityInformation($route);
}
/**
* Gets the list of all entity types.
*
* @return \Drupal\Core\Entity\EntityTypeInterface[]
*/
protected function getEntityTypes() {
if (!isset($this->entityTypes)) {
$this->entityTypes = $this->entityManager->getDefinitions();
}
return $this->entityTypes;
}
}

View file

@ -0,0 +1,499 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\EntityStorageBase.
*/
namespace Drupal\Core\Entity;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Entity\Query\QueryInterface;
/**
* A base entity storage class.
*/
abstract class EntityStorageBase extends EntityHandlerBase implements EntityStorageInterface, EntityHandlerInterface {
/**
* Static cache of entities, keyed by entity ID.
*
* @var array
*/
protected $entities = array();
/**
* Entity type ID for this storage.
*
* @var string
*/
protected $entityTypeId;
/**
* Information about the entity type.
*
* The following code returns the same object:
* @code
* \Drupal::entityManager()->getDefinition($this->entityTypeId)
* @endcode
*
* @var \Drupal\Core\Entity\EntityTypeInterface
*/
protected $entityType;
/**
* Name of the entity's ID field in the entity database table.
*
* @var string
*/
protected $idKey;
/**
* Name of entity's UUID database table field, if it supports UUIDs.
*
* Has the value FALSE if this entity does not use UUIDs.
*
* @var string
*/
protected $uuidKey;
/**
* The name of the entity langcode property.
*
* @var string
*/
protected $langcodeKey;
/**
* The UUID service.
*
* @var \Drupal\Component\Uuid\UuidInterface
*/
protected $uuidService;
/**
* Name of the entity class.
*
* @var string
*/
protected $entityClass;
/**
* Constructs an EntityStorageBase instance.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type definition.
*/
public function __construct(EntityTypeInterface $entity_type) {
$this->entityTypeId = $entity_type->id();
$this->entityType = $entity_type;
$this->idKey = $this->entityType->getKey('id');
$this->uuidKey = $this->entityType->getKey('uuid');
$this->langcodeKey = $this->entityType->getKey('langcode');
$this->entityClass = $this->entityType->getClass();
}
/**
* {@inheritdoc}
*/
public function getEntityTypeId() {
return $this->entityTypeId;
}
/**
* {@inheritdoc}
*/
public function getEntityType() {
return $this->entityType;
}
/**
* {@inheritdoc}
*/
public function loadUnchanged($id) {
$this->resetCache(array($id));
return $this->load($id);
}
/**
* {@inheritdoc}
*/
public function resetCache(array $ids = NULL) {
if ($this->entityType->isStaticallyCacheable() && isset($ids)) {
foreach ($ids as $id) {
unset($this->entities[$id]);
}
}
else {
$this->entities = array();
}
}
/**
* Gets entities from the static cache.
*
* @param array $ids
* If not empty, return entities that match these IDs.
*
* @return \Drupal\Core\Entity\EntityInterface[]
* Array of entities from the entity cache.
*/
protected function getFromStaticCache(array $ids) {
$entities = array();
// Load any available entities from the internal cache.
if ($this->entityType->isStaticallyCacheable() && !empty($this->entities)) {
$entities += array_intersect_key($this->entities, array_flip($ids));
}
return $entities;
}
/**
* Stores entities in the static entity cache.
*
* @param \Drupal\Core\Entity\EntityInterface[] $entities
* Entities to store in the cache.
*/
protected function setStaticCache(array $entities) {
if ($this->entityType->isStaticallyCacheable()) {
$this->entities += $entities;
}
}
/**
* Invokes a hook on behalf of the entity.
*
* @param string $hook
* One of 'presave', 'insert', 'update', 'predelete', 'delete', or
* 'revision_delete'.
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity object.
*/
protected function invokeHook($hook, EntityInterface $entity) {
// Invoke the hook.
$this->moduleHandler()->invokeAll($this->entityTypeId . '_' . $hook, array($entity));
// Invoke the respective entity-level hook.
$this->moduleHandler()->invokeAll('entity_' . $hook, array($entity));
}
/**
* {@inheritdoc}
*/
public function create(array $values = array()) {
$entity_class = $this->entityClass;
$entity_class::preCreate($this, $values);
// Assign a new UUID if there is none yet.
if ($this->uuidKey && $this->uuidService && !isset($values[$this->uuidKey])) {
$values[$this->uuidKey] = $this->uuidService->generate();
}
$entity = $this->doCreate($values);
$entity->enforceIsNew();
$entity->postCreate($this);
// Modules might need to add or change the data initially held by the new
// entity object, for instance to fill-in default values.
$this->invokeHook('create', $entity);
return $entity;
}
/**
* Performs storage-specific creation of entities.
*
* @param array $values
* An array of values to set, keyed by property name.
*
* @return \Drupal\Core\Entity\EntityInterface
*/
protected function doCreate(array $values) {
return new $this->entityClass($values, $this->entityTypeId);
}
/**
* {@inheritdoc}
*/
public function load($id) {
$entities = $this->loadMultiple(array($id));
return isset($entities[$id]) ? $entities[$id] : NULL;
}
/**
* {@inheritdoc}
*/
public function loadMultiple(array $ids = NULL) {
$entities = array();
// Create a new variable which is either a prepared version of the $ids
// array for later comparison with the entity cache, or FALSE if no $ids
// were passed. The $ids array is reduced as items are loaded from cache,
// and we need to know if it's empty for this reason to avoid querying the
// database when all requested entities are loaded from cache.
$passed_ids = !empty($ids) ? array_flip($ids) : FALSE;
// Try to load entities from the static cache, if the entity type supports
// static caching.
if ($this->entityType->isStaticallyCacheable() && $ids) {
$entities += $this->getFromStaticCache($ids);
// If any entities were loaded, remove them from the ids still to load.
if ($passed_ids) {
$ids = array_keys(array_diff_key($passed_ids, $entities));
}
}
// Load any remaining entities from the database. This is the case if $ids
// is set to NULL (so we load all entities) or if there are any ids left to
// load.
if ($ids === NULL || $ids) {
$queried_entities = $this->doLoadMultiple($ids);
}
// Pass all entities loaded from the database through $this->postLoad(),
// which attaches fields (if supported by the entity type) and calls the
// entity type specific load callback, for example hook_node_load().
if (!empty($queried_entities)) {
$this->postLoad($queried_entities);
$entities += $queried_entities;
}
if ($this->entityType->isStaticallyCacheable()) {
// Add entities to the cache.
if (!empty($queried_entities)) {
$this->setStaticCache($queried_entities);
}
}
// Ensure that the returned array is ordered the same as the original
// $ids array if this was passed in and remove any invalid ids.
if ($passed_ids) {
// Remove any invalid ids from the array.
$passed_ids = array_intersect_key($passed_ids, $entities);
foreach ($entities as $entity) {
$passed_ids[$entity->id()] = $entity;
}
$entities = $passed_ids;
}
return $entities;
}
/**
* Performs storage-specific loading of entities.
*
* Override this method to add custom functionality directly after loading.
* This is always called, while self::postLoad() is only called when there are
* actual results.
*
* @param array|null $ids
* (optional) An array of entity IDs, or NULL to load all entities.
*
* @return \Drupal\Core\Entity\EntityInterface[]
* Associative array of entities, keyed on the entity ID.
*/
abstract protected function doLoadMultiple(array $ids = NULL);
/**
* Attaches data to entities upon loading.
*
* @param array $entities
* Associative array of query results, keyed on the entity ID.
*/
protected function postLoad(array &$entities) {
$entity_class = $this->entityClass;
$entity_class::postLoad($this, $entities);
// Call hook_entity_load().
foreach ($this->moduleHandler()->getImplementations('entity_load') as $module) {
$function = $module . '_entity_load';
$function($entities, $this->entityTypeId);
}
// Call hook_TYPE_load().
foreach ($this->moduleHandler()->getImplementations($this->entityTypeId . '_load') as $module) {
$function = $module . '_' . $this->entityTypeId . '_load';
$function($entities);
}
}
/**
* Maps from storage records to entity objects.
*
* @param array $records
* Associative array of query results, keyed on the entity ID.
*
* @return \Drupal\Core\Entity\EntityInterface[]
* An array of entity objects implementing the EntityInterface.
*/
protected function mapFromStorageRecords(array $records) {
$entities = array();
foreach ($records as $record) {
$entity = new $this->entityClass($record, $this->entityTypeId);
$entities[$entity->id()] = $entity;
}
return $entities;
}
/**
* Determines if this entity already exists in storage.
*
* @param int|string $id
* The original entity ID.
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity being saved.
*
* @return bool
*/
abstract protected function has($id, EntityInterface $entity);
/**
* {@inheritdoc}
*/
public function delete(array $entities) {
if (!$entities) {
// If no entities were passed, do nothing.
return;
}
// Allow code to run before deleting.
$entity_class = $this->entityClass;
$entity_class::preDelete($this, $entities);
foreach ($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));
// Allow code to run after deleting.
$entity_class::postDelete($this, $entities);
foreach ($entities as $entity) {
$this->invokeHook('delete', $entity);
}
}
/**
* Performs storage-specific entity deletion.
*
* @param \Drupal\Core\Entity\EntityInterface[] $entities
* An array of entity objects to delete.
*/
abstract protected function doDelete($entities);
/**
* {@inheritdoc}
*/
public function save(EntityInterface $entity) {
$id = $entity->id();
// Track the original ID.
if ($entity->getOriginalId() !== NULL) {
$id = $entity->getOriginalId();
}
// Track if this entity is new.
$is_new = $entity->isNew();
// Track if this entity exists already.
$id_exists = $this->has($id, $entity);
// A new entity should not already exist.
if ($id_exists && $is_new) {
throw new EntityStorageException(SafeMarkup::format('@type entity with ID @id already exists.', array('@type' => $this->entityTypeId, '@id' => $id)));
}
// Load the original entity, if any.
if ($id_exists && !isset($entity->original)) {
$entity->original = $this->loadUnchanged($id);
}
// Allow code to run before saving.
$entity->preSave($this);
$this->invokeHook('presave', $entity);
// Perform the save and reset the static cache for the changed entity.
$return = $this->doSave($id, $entity);
$this->resetCache(array($id));
// The entity is no longer new.
$entity->enforceIsNew(FALSE);
// Allow code to run after saving.
$entity->postSave($this, !$is_new);
$this->invokeHook($is_new ? 'insert' : 'update', $entity);
// After saving, this is now the "original entity", and subsequent saves
// will be updates instead of inserts, and updates must always be able to
// correctly identify the original entity.
$entity->setOriginalId($entity->id());
unset($entity->original);
return $return;
}
/**
* Performs storage-specific saving of the entity.
*
* @param int|string $id
* The original entity ID.
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity to save.
*
* @return bool|int
* If the record insert or update failed, returns FALSE. If it succeeded,
* returns SAVED_NEW or SAVED_UPDATED, depending on the operation performed.
*/
abstract protected function doSave($id, EntityInterface $entity);
/**
* Builds an entity query.
*
* @param \Drupal\Core\Entity\Query\QueryInterface $entity_query
* EntityQuery instance.
* @param array $values
* An associative array of properties of the entity, where the keys are the
* property names and the values are the values those properties must have.
*/
protected function buildPropertyQuery(QueryInterface $entity_query, array $values) {
foreach ($values as $name => $value) {
// Cast scalars to array so we can consistently use an IN condition.
$entity_query->condition($name, (array) $value, 'IN');
}
}
/**
* {@inheritdoc}
*/
public function loadByProperties(array $values = array()) {
// Build a query to fetch the entity IDs.
$entity_query = $this->getQuery();
$this->buildPropertyQuery($entity_query, $values);
$result = $entity_query->execute();
return $result ? $this->loadMultiple($result) : array();
}
/**
* {@inheritdoc}
*/
public function getQuery($conjunction = 'AND') {
// Access the service directly rather than entity.query factory so the
// storage's current entity type is used.
return \Drupal::service($this->getQueryServiceName())->get($this->entityType, $conjunction);
}
/**
* {@inheritdoc}
*/
public function getAggregateQuery($conjunction = 'AND') {
// Access the service directly rather than entity.query factory so the
// storage's current entity type is used.
return \Drupal::service($this->getQueryServiceName())->getAggregate($this->entityType, $conjunction);
}
/**
* Gets the name of the service for the query for this entity storage.
*
* @return string
* The name of the service for the query for this entity storage.
*/
abstract protected function getQueryServiceName();
}

View file

@ -0,0 +1,13 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\EntityStorageException.
*/
namespace Drupal\Core\Entity;
/**
* Defines an exception thrown when storage operations fail.
*/
class EntityStorageException extends \Exception { }

View file

@ -0,0 +1,196 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\EntityStorageInterface.
*/
namespace Drupal\Core\Entity;
/**
* Defines the interface for entity storage classes.
*
* For common default implementations, see
* \Drupal\Core\Entity\Sql\SqlContentEntityStorage for content entities and
* \Drupal\Core\Config\Entity\ConfigEntityStorage for config entities. Those
* implementations are used by default when the @ContentEntityType or
* @ConfigEntityType annotations are used.
*
* @ingroup entity_api
*/
interface EntityStorageInterface {
/**
* Load the most recent version of an entity's field data.
*/
const FIELD_LOAD_CURRENT = 'FIELD_LOAD_CURRENT';
/**
* Load the version of an entity's field data specified in the entity.
*/
const FIELD_LOAD_REVISION = 'FIELD_LOAD_REVISION';
/**
* Resets the internal, static entity cache.
*
* @param $ids
* (optional) If specified, the cache is reset for the entities with the
* given ids only.
*/
public function resetCache(array $ids = NULL);
/**
* Loads one or more entities.
*
* @param $ids
* An array of entity IDs, or NULL to load all entities.
*
* @return \Drupal\Core\Entity\EntityInterface[]
* An array of entity objects indexed by their IDs. Returns an empty array
* if no matching entities are found.
*/
public function loadMultiple(array $ids = NULL);
/**
* Loads one entity.
*
* @param mixed $id
* The ID of the entity to load.
*
* @return \Drupal\Core\Entity\EntityInterface|null
* An entity object. NULL if no matching entity is found.
*/
public function load($id);
/**
* Loads an unchanged entity from the database.
*
* @param mixed $id
* The ID of the entity to load.
*
* @return \Drupal\Core\Entity\EntityInterface|null
* The unchanged entity, or NULL if the entity cannot be loaded.
*
* @todo Remove this method once we have a reliable way to retrieve the
* unchanged entity from the entity object.
*/
public function loadUnchanged($id);
/**
* Load a specific entity revision.
*
* @param int $revision_id
* The revision id.
*
* @return \Drupal\Core\Entity\EntityInterface|null
* The specified entity revision or NULL if not found.
*/
public function loadRevision($revision_id);
/**
* Delete a specific entity revision.
*
* A revision can only be deleted if it's not the currently active one.
*
* @param int $revision_id
* The revision id.
*/
public function deleteRevision($revision_id);
/**
* Load entities by their property values.
*
* @param array $values
* An associative array where the keys are the property names and the
* values are the values those properties must have.
*
* @return \Drupal\Core\Entity\EntityInterface[]
* An array of entity objects indexed by their ids.
*/
public function loadByProperties(array $values = array());
/**
* Constructs a new entity object, without permanently saving it.
*
* @param array $values
* (optional) An array of values to set, keyed by property name. If the
* entity type has bundles, the bundle key has to be specified.
*
* @return \Drupal\Core\Entity\EntityInterface
* A new entity object.
*/
public function create(array $values = array());
/**
* Deletes permanently saved entities.
*
* @param array $entities
* An array of entity objects to delete.
*
* @throws \Drupal\Core\Entity\EntityStorageException
* In case of failures, an exception is thrown.
*/
public function delete(array $entities);
/**
* Saves the entity permanently.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity to save.
*
* @return
* SAVED_NEW or SAVED_UPDATED is returned depending on the operation
* performed.
*
* @throws \Drupal\Core\Entity\EntityStorageException
* In case of failures, an exception is thrown.
*/
public function save(EntityInterface $entity);
/**
* Gets an entity query instance.
*
* @param string $conjunction
* (optional) The logical operator for the query, either:
* - AND: all of the conditions on the query need to match.
* - OR: at least one of the conditions on the query need to match.
*
* @return \Drupal\Core\Entity\Query\QueryInterface
* The query instance.
*
* @see \Drupal\Core\Entity\EntityStorageBase::getQueryServiceName()
*/
public function getQuery($conjunction = 'AND');
/**
* Gets an aggregated query instance.
*
* @param string $conjunction
* (optional) The logical operator for the query, either:
* - AND: all of the conditions on the query need to match.
* - OR: at least one of the conditions on the query need to match.
*
* @return \Drupal\Core\Entity\Query\QueryAggregateInterface
* The aggregated query object that can query the given entity type.
*
* @see \Drupal\Core\Entity\EntityStorageBase::getQueryServiceName()
*/
public function getAggregateQuery($conjunction = 'AND');
/**
* Gets the entity type ID.
*
* @return string
* The entity type ID.
*/
public function getEntityTypeId();
/**
* Gets the entity type definition.
*
* @return \Drupal\Core\Entity\EntityTypeInterface
* Entity type definition.
*/
public function getEntityType();
}

View file

@ -0,0 +1,773 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\EntityType.
*/
namespace Drupal\Core\Entity;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Entity\Exception\EntityTypeIdLengthException;
use Drupal\Core\StringTranslation\StringTranslationTrait;
/**
* Provides an implementation of an entity type and its metadata.
*
* @ingroup entity_api
*/
class EntityType implements EntityTypeInterface {
use StringTranslationTrait;
/**
* Indicates whether entities should be statically cached.
*
* @var bool
*/
protected $static_cache = TRUE;
/**
* Indicates whether the rendered output of entities should be cached.
*
* @var bool
*/
protected $render_cache = TRUE;
/**
* Indicates if the persistent cache of field data should be used.
*
* @var bool
*/
protected $persistent_cache = TRUE;
/**
* An array of entity keys.
*
* @var array
*/
protected $entity_keys = array();
/**
* The unique identifier of this entity type.
*
* @var string
*/
protected $id;
/**
* The name of the provider of this entity type.
*
* @var string
*/
protected $provider;
/**
* The name of the entity type class.
*
* @var string
*/
protected $class;
/**
* The name of the original entity type class.
*
* This is only set if the class name is changed.
*
* @var string
*/
protected $originalClass;
/**
* An array of handlers.
*
* @var array
*/
protected $handlers = array();
/**
* The name of the default administrative permission.
*
* @var string
*/
protected $admin_permission;
/**
* The permission granularity level.
*
* The allowed values are respectively "entity_type" or "bundle".
*
* @var string
*/
protected $permission_granularity = 'entity_type';
/**
* Link templates using the URI template syntax.
*
* @var array
*/
protected $links = array();
/**
* The name of a callback that returns the label of the entity.
*
* @var string|null
*/
protected $label_callback = NULL;
/**
* The name of the entity type which provides bundles.
*
* @var string
*/
protected $bundle_entity_type = 'bundle';
/**
* The name of the entity type for which bundles are provided.
*
* @var string|null
*/
protected $bundle_of = NULL;
/**
* The human-readable name of the entity bundles, e.g. Vocabulary.
*
* @var string|null
*/
protected $bundle_label = NULL;
/**
* The name of the entity type's base table.
*
* @var string|null
*/
protected $base_table = NULL;
/**
* The name of the entity type's revision data table.
*
* @var string|null
*/
protected $revision_data_table = NULL;
/**
* The name of the entity type's revision table.
*
* @var string|null
*/
protected $revision_table = NULL;
/**
* The name of the entity type's data table.
*
* @var string|null
*/
protected $data_table = NULL;
/**
* Indicates whether entities of this type have multilingual support.
*
* @var bool
*/
protected $translatable = FALSE;
/**
* The human-readable name of the type.
*
* @var string
*/
protected $label = '';
/**
* A callable that can be used to provide the entity URI.
*
* @var callable|null
*/
protected $uri_callback = NULL;
/**
* The machine name of the entity type group.
*/
protected $group;
/**
* The human-readable name of the entity type group.
*/
protected $group_label;
/**
* The route name used by field UI to attach its management pages.
*
* @var string
*/
protected $field_ui_base_route;
/**
* Indicates whether this entity type is commonly used as a reference target.
*
* This is used by the Entity reference field to promote an entity type in the
* add new field select list in Field UI.
*
* @var bool
*/
protected $common_reference_target = FALSE;
/**
* The list cache contexts for this entity type.
*
* @var string[]
*/
protected $list_cache_contexts = [];
/**
* The list cache tags for this entity type.
*
* @var string[]
*/
protected $list_cache_tags = [];
/**
* Entity constraint definitions.
*
* @var array[]
*/
protected $constraints = array();
/**
* Constructs a new EntityType.
*
* @param array $definition
* An array of values from the annotation.
*
* @throws \Drupal\Core\Entity\Exception\EntityTypeIdLengthException
* Thrown when attempting to instantiate an entity type with too long ID.
*/
public function __construct($definition) {
// Throw an exception if the entity type ID is longer than 32 characters.
if (Unicode::strlen($definition['id']) > static::ID_MAX_LENGTH) {
throw new EntityTypeIdLengthException(SafeMarkup::format(
'Attempt to create an entity type with an ID longer than @max characters: @id.', array(
'@max' => static::ID_MAX_LENGTH,
'@id' => $definition['id'],
)
));
}
foreach ($definition as $property => $value) {
$this->{$property} = $value;
}
// Ensure defaults.
$this->entity_keys += array(
'revision' => '',
'bundle' => '',
'langcode' => '',
'default_langcode' => 'default_langcode',
);
$this->handlers += array(
'access' => 'Drupal\Core\Entity\EntityAccessControlHandler',
);
// Automatically add the EntityChanged constraint if the entity type tracks
// the changed time.
if ($this->isSubclassOf('Drupal\Core\Entity\EntityChangedInterface') ) {
$this->addConstraint('EntityChanged');
}
// Ensure a default list cache tag is set.
if (empty($this->list_cache_tags)) {
$this->list_cache_tags = [$definition['id'] . '_list'];
}
}
/**
* {@inheritdoc}
*/
public function get($property) {
return isset($this->{$property}) ? $this->{$property} : NULL;
}
/**
* {@inheritdoc}
*/
public function set($property, $value) {
$this->{$property} = $value;
return $this;
}
/**
* {@inheritdoc}
*/
public function isStaticallyCacheable() {
return $this->static_cache;
}
/**
* {@inheritdoc}
*/
public function isRenderCacheable() {
return $this->render_cache;
}
/**
* {@inheritdoc}
*/
public function isPersistentlyCacheable() {
return $this->persistent_cache;
}
/**
* {@inheritdoc}
*/
public function getKeys() {
return $this->entity_keys;
}
/**
* {@inheritdoc}
*/
public function getKey($key) {
$keys = $this->getKeys();
return isset($keys[$key]) ? $keys[$key] : FALSE;
}
/**
* {@inheritdoc}
*/
public function hasKey($key) {
$keys = $this->getKeys();
return !empty($keys[$key]);
}
/**
* {@inheritdoc}
*/
public function id() {
return $this->id;
}
/**
* {@inheritdoc}
*/
public function getProvider() {
return $this->provider;
}
/**
* {@inheritdoc}
*/
public function getClass() {
return $this->class;
}
/**
* {@inheritdoc}
*/
public function getOriginalClass() {
return $this->originalClass ?: $this->class;
}
/**
* {@inheritdoc}
*/
public function setClass($class) {
if (!$this->originalClass && $this->class) {
// If the original class is currently not set, set it to the current
// class, assume that is the original class name.
$this->originalClass = $this->class;
}
$this->class = $class;
return $this;
}
/**
* {@inheritdoc}
*/
public function isSubclassOf($class) {
return is_subclass_of($this->getClass(), $class);
}
/**
* {@inheritdoc}
*/
public function getHandlerClasses() {
return $this->handlers;
}
/**
* {@inheritdoc}
*/
public function getHandlerClass($handler_type, $nested = FALSE) {
if ($this->hasHandlerClass($handler_type, $nested)) {
$handlers = $this->getHandlerClasses();
return $nested ? $handlers[$handler_type][$nested] : $handlers[$handler_type];
}
}
/**
* {@inheritdoc}
*/
public function setHandlerClass($handler_type, $value) {
$this->handlers[$handler_type] = $value;
return $this;
}
/**
* {@inheritdoc}
*/
public function hasHandlerClass($handler_type, $nested = FALSE) {
$handlers = $this->getHandlerClasses();
if (!isset($handlers[$handler_type]) || ($nested && !isset($handlers[$handler_type][$nested]))) {
return FALSE;
}
$handler = $handlers[$handler_type];
if ($nested) {
$handler = $handler[$nested];
}
return class_exists($handler);
}
/**
* {@inheritdoc}
*/
public function getStorageClass() {
return $this->getHandlerClass('storage');
}
/**
* {@inheritdoc}
*/
public function setStorageClass($class) {
$this->handlers['storage'] = $class;
}
/**
* {@inheritdoc}
*/
public function getFormClass($operation) {
return $this->getHandlerClass('form', $operation);
}
/**
* {@inheritdoc}
*/
public function setFormClass($operation, $class) {
$this->handlers['form'][$operation] = $class;
return $this;
}
/**
* {@inheritdoc}
*/
public function hasFormClasses() {
return !empty($this->handlers['form']);
}
/**
* {@inheritdoc}
*/
public function hasRouteProviders() {
return !empty($this->handlers['route_provider']);
}
/**
* {@inheritdoc}
*/
public function getListBuilderClass() {
return $this->getHandlerClass('list_builder');
}
/**
* {@inheritdoc}
*/
public function setListBuilderClass($class) {
$this->handlers['list_builder'] = $class;
return $this;
}
/**
* {@inheritdoc}
*/
public function hasListBuilderClass() {
return $this->hasHandlerClass('list_builder');
}
/**
* {@inheritdoc}
*/
public function getViewBuilderClass() {
return $this->getHandlerClass('view_builder');
}
/**
* {@inheritdoc}
*/
public function setViewBuilderClass($class) {
$this->handlers['view_builder'] = $class;
return $this;
}
/**
* {@inheritdoc}
*/
public function hasViewBuilderClass() {
return $this->hasHandlerClass('view_builder');
}
/**
* {@inheritdoc}
*/
public function getRouteProviderClasses() {
return !empty($this->handlers['route_provider']) ? $this->handlers['route_provider'] : [];
}
/**
* {@inheritdoc}
*/
public function getAccessControlClass() {
return $this->getHandlerClass('access');
}
/**
* {@inheritdoc}
*/
public function setAccessClass($class) {
$this->handlers['access'] = $class;
return $this;
}
/**
* {@inheritdoc}
*/
public function getAdminPermission() {
return $this->admin_permission ?: FALSE;
}
/**
* {@inheritdoc}
*/
public function getPermissionGranularity() {
return $this->permission_granularity;
}
/**
* {@inheritdoc}
*/
public function getLinkTemplates() {
return $this->links;
}
/**
* {@inheritdoc}
*/
public function getLinkTemplate($key) {
$links = $this->getLinkTemplates();
return isset($links[$key]) ? $links[$key] : FALSE;
}
/**
* {@inheritdoc}
*/
public function hasLinkTemplate($key) {
$links = $this->getLinkTemplates();
return isset($links[$key]);
}
/**
* {@inheritdoc}
*/
public function setLinkTemplate($key, $path) {
if ($path[0] !== '/') {
throw new \InvalidArgumentException('Link templates accepts paths, which have to start with a leading slash.');
}
$this->links[$key] = $path;
return $this;
}
/**
* {@inheritdoc}
*/
public function getLabelCallback() {
return $this->label_callback;
}
/**
* {@inheritdoc}
*/
public function setLabelCallback($callback) {
$this->label_callback = $callback;
return $this;
}
/**
* {@inheritdoc}
*/
public function hasLabelCallback() {
return isset($this->label_callback);
}
/**
* {@inheritdoc}
*/
public function getBundleEntityType() {
return $this->bundle_entity_type;
}
/**
* {@inheritdoc}
*/
public function getBundleOf() {
return $this->bundle_of;
}
/**
* {@inheritdoc}
*/
public function getBundleLabel() {
return (string) $this->bundle_label;
}
/**
* {@inheritdoc}
*/
public function getBaseTable() {
return $this->base_table;
}
/**
* {@inheritdoc}
*/
public function isTranslatable() {
return !empty($this->translatable);
}
/**
* {@inheritdoc}
*/
public function isRevisionable() {
// Entity types are revisionable if a revision key has been specified.
return $this->hasKey('revision');
}
/**
* {@inheritdoc}
*/
public function getRevisionDataTable() {
return $this->revision_data_table;
}
/**
* {@inheritdoc}
*/
public function getRevisionTable() {
return $this->revision_table;
}
/**
* {@inheritdoc}
*/
public function getDataTable() {
return $this->data_table;
}
/**
* {@inheritdoc}
*/
public function getLabel() {
return (string) $this->label;
}
/**
* {@inheritdoc}
*/
public function getLowercaseLabel() {
return Unicode::strtolower($this->getLabel());
}
/**
* {@inheritdoc}
*/
public function getUriCallback() {
return $this->uri_callback;
}
/**
* {@inheritdoc}
*/
public function setUriCallback($callback) {
$this->uri_callback = $callback;
return $this;
}
/**
* {@inheritdoc}
*/
public function getGroup() {
return $this->group;
}
/**
* {@inheritdoc}
*/
public function getGroupLabel() {
return !empty($this->group_label) ? (string) $this->group_label : $this->t('Other', array(), array('context' => 'Entity type group'));
}
/**
* {@inheritdoc}
*/
public function getListCacheContexts() {
return $this->list_cache_contexts;
}
/**
* {@inheritdoc}
*/
public function getListCacheTags() {
return $this->list_cache_tags;
}
/**
* {@inheritdoc}
*/
public function getConfigDependencyKey() {
// Return 'content' for the default implementation as important distinction
// is that dependencies on other configuration entities are hard
// dependencies and have to exist before creating the dependent entity.
return 'content';
}
/**
* {@inheritdoc}
*/
public function isCommonReferenceTarget() {
return $this->common_reference_target;
}
/**
* {@inheritdoc}
*/
public function getConstraints() {
return $this->constraints;
}
/**
* {@inheritdoc}
*/
public function setConstraints(array $constraints) {
$this->constraints = $constraints;
return $this;
}
/**
* {@inheritdoc}
*/
public function addConstraint($constraint_name, $options = NULL) {
$this->constraints[$constraint_name] = $options;
return $this;
}
}

View file

@ -0,0 +1,63 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\EntityTypeEvent.
*/
namespace Drupal\Core\Entity;
use Symfony\Component\EventDispatcher\GenericEvent;
/**
* Defines a base class for all entity type events.
*/
class EntityTypeEvent extends GenericEvent {
/**
* The entity type.
*
* @var \Drupal\Core\Entity\EntityTypeInterface
*/
protected $entityType;
/**
* The original entity type.
*
* @var \Drupal\Core\Entity\EntityTypeInterface
*/
protected $original;
/**
* Constructs a new EntityTypeEvent.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The field storage definition.
* @param \Drupal\Core\Entity\EntityTypeInterface $original
* (optional) The original entity type. This should be passed only when
* updating the entity type.
*/
public function __construct(EntityTypeInterface $entity_type, EntityTypeInterface $original = NULL) {
$this->entityType = $entity_type;
$this->original = $original;
}
/**
* The entity type the event refers to.
*
* @return \Drupal\Core\Entity\EntityTypeInterface
*/
public function getEntityType() {
return $this->entityType;
}
/**
* The original entity type.
*
* @return \Drupal\Core\Entity\EntityTypeInterface
*/
public function getOriginal() {
return $this->original;
}
}

View file

@ -0,0 +1,79 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\EntityTypeEventSubscriberTrait.
*/
namespace Drupal\Core\Entity;
/**
* Helper methods for EntityTypeListenerInterface.
*
* This allows a class implementing EntityTypeListenerInterface to subscribe and
* react to entity type events.
*
* @see \Symfony\Component\EventDispatcher\EventSubscriberInterface
* @see \Drupal\Core\Entity\EntityTypeListenerInterface
*/
trait EntityTypeEventSubscriberTrait {
/**
* Gets the subscribed events.
*
* @return array
* An array of subscribed event names.
*
* @see \Symfony\Component\EventDispatcher\EventSubscriberInterface::getSubscribedEvents()
*/
public static function getEntityTypeEvents() {
$event = array('onEntityTypeEvent', 100);
$events[EntityTypeEvents::CREATE][] = $event;
$events[EntityTypeEvents::UPDATE][] = $event;
$events[EntityTypeEvents::DELETE][] = $event;
return $events;
}
/**
* Listener method for any entity type definition event.
*
* @param \Drupal\Core\Entity\EntityTypeEvent $event
* The field storage definition event object.
* @param string $event_name
* The event name.
*/
public function onEntityTypeEvent(EntityTypeEvent $event, $event_name) {
switch ($event_name) {
case EntityTypeEvents::CREATE:
$this->onEntityTypeCreate($event->getEntityType());
break;
case EntityTypeEvents::UPDATE:
$this->onEntityTypeUpdate($event->getEntityType(), $event->getOriginal());
break;
case EntityTypeEvents::DELETE:
$this->onEntityTypeDelete($event->getEntityType());
break;
}
}
/**
* {@inheritdoc}
*/
public function onEntityTypeCreate(EntityTypeInterface $entity_type) {
}
/**
* {@inheritdoc}
*/
public function onEntityTypeUpdate(EntityTypeInterface $entity_type, EntityTypeInterface $original) {
}
/**
* {@inheritdoc}
*/
public function onEntityTypeDelete(EntityTypeInterface $entity_type) {
}
}

View file

@ -0,0 +1,69 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\EntityTypeEvents.
*/
namespace Drupal\Core\Entity;
/**
* Contains all events thrown while handling entity types.
*/
final class EntityTypeEvents {
/**
* The name of the event triggered when a new entity type is created.
*
* This event allows modules to react to a new entity type being created. The
* event listener method receives a \Drupal\Core\Entity\EntityTypeEvent
* instance.
*
* @Event
*
* @see \Drupal\Core\Entity\EntityTypeEvent
* @see \Drupal\Core\Entity\EntityManager::onEntityTypeCreate()
* @see \Drupal\Core\Entity\EntityTypeEventSubscriberTrait
* @see \Drupal\views\EventSubscriber\ViewsEntitySchemaSubscriber::onEntityTypeCreate()
*
* @var string
*/
const CREATE = 'entity_type.definition.create';
/**
* The name of the event triggered when an existing entity type is updated.
*
* This event allows modules to react whenever an existing entity type is
* updated. The event listener method receives a
* \Drupal\Core\Entity\EntityTypeEvent instance.
*
* @Event
*
* @see \Drupal\Core\Entity\EntityTypeEvent
* @see \Drupal\Core\Entity\EntityManager::onEntityTypeUpdate()
* @see \Drupal\Core\Entity\EntityTypeEventSubscriberTrait
* @see \Drupal\views\EventSubscriber\ViewsEntitySchemaSubscriber::onEntityTypeUpdate()
*
* @var string
*/
const UPDATE = 'entity_type.definition.update';
/**
* The name of the event triggered when an existing entity type is deleted.
*
* This event allows modules to react whenever an existing entity type is
* deleted. The event listener method receives a
* \Drupal\Core\Entity\EntityTypeEvent instance.
*
* @Event
*
* @see \Drupal\Core\Entity\EntityTypeEvent
* @see \Drupal\Core\Entity\EntityManager::onEntityTypeDelete()
* @see \Drupal\Core\Entity\EntityTypeEventSubscriberTrait
* @see \Drupal\views\EventSubscriber\ViewsEntitySchemaSubscriber::onEntityTypeDelete()
*
* @var string
*/
const DELETE = 'entity_type.definition.delete';
}

View file

@ -0,0 +1,735 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\EntityTypeInterface.
*/
namespace Drupal\Core\Entity;
/**
* Provides an interface for an entity type and its metadata.
*
* Additional information can be provided by modules: hook_entity_type_build() can be
* implemented to define new properties, while hook_entity_type_alter() can be
* implemented to alter existing data and fill-in defaults. Module-specific
* properties should be documented in the hook implementations defining them.
*/
interface EntityTypeInterface {
/**
* The maximum length of ID, in characters.
*/
const ID_MAX_LENGTH = 32;
/**
* The maximum length of bundle name, in characters.
*/
const BUNDLE_MAX_LENGTH = 32;
/**
* Gets any arbitrary property.
*
* @param string $property
* The property to retrieve.
*
* @return mixed
* The value for that property, or NULL if the property does not exist.
*/
public function get($property);
/**
* Sets a value to an arbitrary property.
*
* @param string $property
* The property to use for the value.
* @param mixed $value
* The value to set.
*
* @return $this
*/
public function set($property, $value);
/**
* Gets the unique identifier of the entity type.
*
* @return string
* The unique identifier of the entity type.
*/
public function id();
/**
* Gets the name of the provider of this entity type.
*
* @return string
* The name of the provider of this entity type.
*/
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.
*
* In case the class name was changed with setClass(), this will return
* the initial value. Useful when trying to identify the entity type ID based
* on the class.
*
* @return string
* The name of the original entity type class.
*/
public function getOriginalClass();
/**
* Gets an array of entity keys.
*
* @return array
* An array describing how the Field API can extract certain information
* from objects of this entity type:
* - id: The name of the property that contains the primary ID of the
* entity. Every entity object passed to the Field API must have this
* property and its value must be numeric.
* - revision: (optional) The name of the property that contains the
* revision ID of the entity. The Field API assumes that all revision IDs
* are unique across all entities of a type. If this entry is omitted
* the entities of this type are not revisionable.
* - bundle: (optional) The name of the property that contains the bundle
* name for the entity. The bundle name defines which set of fields are
* attached to the entity (e.g. what nodes call "content type"). This
* entry can be omitted if this entity type exposes a single bundle (such
* that all entities have the same collection of fields). The name of this
* single bundle will be the same as the entity type.
* - 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).
* - 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.
* - uuid: (optional) The name of the property that contains the universally
* unique identifier of the entity, which is used to distinctly identify
* an entity across different systems.
*/
public function getKeys();
/**
* Gets a specific entity key.
*
* @param string $key
* The name of the entity key to return.
*
* @return string|bool
* The entity key, or FALSE if it does not exist.
*
* @see self::getKeys()
*/
public function getKey($key);
/**
* Indicates if a given entity key exists.
*
* @param string $key
* The name of the entity key to check.
*
* @return bool
* TRUE if a given entity key exists, FALSE otherwise.
*/
public function hasKey($key);
/**
* Indicates whether entities should be statically cached.
*
* @return bool
* TRUE if static caching should be used; FALSE otherwise.
*/
public function isStaticallyCacheable();
/**
* Indicates whether the rendered output of entities should be cached.
*
* @return bool
*/
public function isRenderCacheable();
/**
* Indicates if the persistent cache of field data should be used.
*
* @todo Used by ContentEntityStorageBase only.
*
* The persistent cache should usually only be disabled if a higher level
* persistent cache is available for the entity type.
*
* @return bool
*/
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.
*
* @param string $handler_type
* The type of handler to check.
* @param bool $nested
* (optional) If this handler has a nested definition. Defaults to FALSE.
*
* @return bool
* TRUE if a handler of this type exists, FALSE otherwise.
*/
public function hasHandlerClass($handler_type, $nested = FALSE);
/**
* @param string $handler_type
* The handler type to get.
*
* @return array|string|null
* The handlers for a given type, or NULL if none exist.
*/
public function getHandlerClass($handler_type);
/**
* Gets an array of handlers.
*
* @return array
* An associative array where the keys are the names of different handler
* types (listed below) and the values are the names of the classes that
* implement that handler:
* - storage: The name of the class used to load the objects. The class must
* implement \Drupal\Core\Entity\EntityStorageInterface.
* - form: An associative array where the keys are the names of the
* different form operations (such as 'create', 'edit', or 'delete') and
* the values are the names of the handler classes for those
* operations. The name of the operation is passed also to the form
* handler's constructor, so that one class can be used for multiple
* entity forms when the forms are similar. The classes must implement
* \Drupal\Core\Entity\EntityFormInterface.
* - list: The name of the class that provides listings of the entities. The
* class must implement \Drupal\Core\Entity\EntityListBuilderInterface.
* - render: The name of the class that is used to render the entities. The
* class must implement \Drupal\Core\Entity\EntityViewBuilderInterface.
* - access: The name of the class that is used for access checks. The class
* must implement \Drupal\Core\Entity\EntityAccessControlHandlerInterface.
* Defaults to \Drupal\Core\Entity\EntityAccessControlHandler.
* - route_provider: (optional) A list of class names, keyed by a group
* string, which will be used to define routes related to this entity
* type. These classes must implement
* \Drupal\Core\Entity\Routing\EntityRouteProviderInterface.
*/
public function getHandlerClasses();
/**
* Gets the storage class.
*
* @return string
* The class for this entity type's storage.
*/
public function getStorageClass();
/**
* Sets the storage class.
*
* @param string $class
* The class for this entity type's storage.
*
* @return $this
*/
public function setStorageClass($class);
/**
* Gets the form class for a specific operation.
*
* @param string $operation
* The name of the operation to use, e.g., 'default'.
*
* @return string
* The class for this operation's form for this entity type.
*
* @see \Drupal\Core\Entity\EntityFormBuilderInterface
*/
public function getFormClass($operation);
/**
* Sets a form class for a specific operation.
*
* @param string $operation
* The operation to use this form class for.
* @param string $class
* The form class implementing
* \Drupal\Core\Entity\EntityFormInterface.
*
* @return $this
*
* @see \Drupal\Core\Entity\EntityFormBuilderInterface
*/
public function setFormClass($operation, $class);
/**
* Indicates if this entity type has any forms.
*
* @return bool
* TRUE if there are any forms for this entity type, FALSE otherwise.
*/
public function hasFormClasses();
/**
* Indicates if this entity type has any route provider.
*
* @return bool
*/
public function hasRouteProviders();
/**
* Gets all the route provide handlers.
*
* Much like forms you can define multiple route provider handlers.
*
* @return string[]
*/
public function getRouteProviderClasses();
/**
* Gets the list class.
*
* @return string
* The class for this entity type's list.
*/
public function getListBuilderClass();
/**
* Sets the list class.
*
* @param string $class
* The list class to use for the operation.
*
* @return $this
*/
public function setListBuilderClass($class);
/**
* Indicates if this entity type has a list class.
*
* @return bool
* TRUE if there is a list for this entity type, FALSE otherwise.
*/
public function hasListBuilderClass();
/**
* Gets the view builder class.
*
* @return string
* The class for this entity type's view builder.
*/
public function getViewBuilderClass();
/**
* Gets the view builder class.
*
* @param string $class
* The class for this entity type's view builder.
*
* @return $this
*/
public function setViewBuilderClass($class);
/**
* Indicates if this entity type has a view builder.
*
* @return bool
* TRUE if there is a view builder for this entity type, FALSE otherwise.
*/
public function hasViewBuilderClass();
/**
* Gets the access control class.
*
* @return string
* The class for this entity type's access control.
*/
public function getAccessControlClass();
/**
* Gets the access class.
*
* @param string $class
* The class for this entity type's access.
*
* @return $this
*/
public function setAccessClass($class);
/**
* Indicates if the entity type is a subclass of the given class or interface.
*
* @param string $class
* The class or interface to check.
*
* @return bool
* TRUE if the entity type is a subclass of the class or interface.
*/
public function isSubclassOf($class);
/**
* Sets the handlers for a given type.
*
* @param string $handler_type
* The type of handler to set.
* @param array|string $value
* The value for a handler type.
*
* @return $this
*/
public function setHandlerClass($handler_type, $value);
/**
* Gets the name of the default administrative permission.
*
* The default \Drupal\Core\Entity\EntityAccessControlHandler class checks this
* permission for all operations in its checkAccess() method. Entities with
* more complex permissions can extend this class to do their own access
* checks.
*
* @return string|bool
*/
public function getAdminPermission();
/**
* Gets the permission granularity level.
*
* The allowed values are respectively "entity_type" or "bundle".
*
* @return string
* Whether a module exposing permissions for the current entity type
* should use entity-type level granularity or bundle level granularity.
*/
public function getPermissionGranularity();
/**
* Gets the link templates using the URI template syntax.
*
* Links are an array of standard link relations to the URI template that
* should be used for them. Where possible, link relationships should use
* established IANA relationships rather than custom relationships.
*
* Every entity type should, at minimum, define "canonical", which is the
* pattern for URIs to that entity. Even if the entity will have no HTML page
* exposed to users it should still have a canonical URI in order to be
* compatible with web services. Entities that will be user-editable via an
* HTML page must also define an "edit-form" relationship.
*
* By default, the following placeholders are supported:
* - [entityType]: The entity type itself will also be a valid token for the
* ID of the entity. For instance, a placeholder of {node} used on the Node
* class.
* - [bundleEntityType]: The bundle machine name itself. For instance, a
* placeholder of {node_type} used on the Node class.
*
* Specific entity types may also expand upon this list by overriding the
* Entity::urlRouteParameters() method.
*
* @link http://www.iana.org/assignments/link-relations/link-relations.xml @endlink
* @link http://tools.ietf.org/html/rfc6570 @endlink
*
* @return array
*/
public function getLinkTemplates();
/**
* Gets the link template for a given key.
*
* @param string $key
* The link type.
*
* @return string|bool
* The path for this link, or FALSE if it doesn't exist.
*/
public function getLinkTemplate($key);
/**
* Indicates if a link template exists for a given key.
*
* @param string $key
* The link type.
*
* @return bool
* TRUE if the link template exists, FALSE otherwise.
*/
public function hasLinkTemplate($key);
/**
* Sets a single link template.
*
* @param string $key
* The name of a link.
* @param string $path
* The route path to use for the link.
*
* @return $this
*
* @throws \InvalidArgumentException
* Thrown when the path does not start with a leading slash.
*/
public function setLinkTemplate($key, $path);
/**
* Gets the callback for the label of the entity.
*
* The function takes an entity and returns the label of the entity. Use
* language() on the entity to get information on the requested language. The
* 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.
*
* @return callable|null
* The callback, or NULL if none exists.
*/
public function getLabelCallback();
/**
* Sets the label callback.
*
* @param callable $callback
* A callable that returns the label of the entity.
*
* @return $this
*/
public function setLabelCallback($callback);
/**
* Indicates if a label callback exists.
*
* @return bool
*/
public function hasLabelCallback();
/**
* Gets the name of the entity type which provides bundles.
*
* @return string
*/
public function getBundleEntityType();
/**
* Gets the entity type for which this entity provides bundles.
*
* It can be used by other modules to act accordingly; for example,
* the Field UI module uses it to add operation links to manage fields and
* displays.
*
* @return string|null
* The entity type for which this entity provides bundles, or NULL if does
* not provide bundles for another entity type.
*/
public function getBundleOf();
/**
* Gets the label for the bundle.
*
* @return string|null
* The bundle label, or NULL if none exists.
*/
public function getBundleLabel();
/**
* Gets the name of the entity's base table.
*
* @todo Used by SqlContentEntityStorage only.
*
* @return string|null
* The name of the entity's base table, or NULL if none exists.
*/
public function getBaseTable();
/**
* Indicates whether entities of this type have multilingual support.
*
* At an entity level, this indicates language support and at a bundle level
* this indicates translation support.
*
* @return bool
*/
public function isTranslatable();
/**
* Indicates whether entities of this type have revision support.
*
* @return bool
*/
public function isRevisionable();
/**
* Gets the name of the entity's revision data table.
*
* @todo Used by SqlContentEntityStorage only.
*
* @return string|null
* The name of the entity type's revision data table, or NULL if none
* exists.
*/
public function getRevisionDataTable();
/**
* Gets the name of the entity's revision table.
*
* @todo Used by SqlContentEntityStorage only.
*
* @return string|null
* The name of the entity type's revision table, or NULL if none exists.
*/
public function getRevisionTable();
/**
* Gets the name of the entity's data table.
*
* @todo Used by SqlContentEntityStorage only.
*
* @return string|null
* The name of the entity type's data table, or NULL if none exists.
*/
public function getDataTable();
/**
* Gets the human-readable name of the entity type.
*
* @return string
* The human-readable name of the entity type.
*/
public function getLabel();
/**
* Gets the lowercase form of the human-readable entity type name.
*
* @return string
* The lowercase form of the human-readable entity type name.
*/
public function getLowercaseLabel();
/**
* Gets a callable that can be used to provide the entity URI.
*
* This is only called if there is no matching link template for the link
* relationship type, and there is no bundle-specific callback provided.
*
* @return callable|null
* A valid callback that is passed the entity or NULL if none is specified.
*/
public function getUriCallback();
/**
* Sets a callable to use to provide the entity URI.
*
* @param callable $callback
* A callback to use to provide a URI for the entity.
*
* @return $this
*/
public function setUriCallback($callback);
/**
* The list cache contexts associated with this entity type.
*
* Enables code listing entities of this type to ensure that rendered listings
* are varied as necessary, typically to ensure users of role A see other
* entities listed than users of role B.
*
* @return string[]
*/
public function getListCacheContexts();
/**
* The list cache tags associated with this entity type.
*
* Enables code listing entities of this type to ensure that newly created
* entities show up immediately.
*
* @return string[]
*/
public function getListCacheTags();
/**
* Gets the key that is used to store configuration dependencies.
*
* @return string
* The key to be used in configuration dependencies when storing
* dependencies on entities of this type.
*/
public function getConfigDependencyKey();
/**
* Indicates whether this entity type is commonly used as a reference target.
*
* @return bool
* TRUE if the entity type is a common reference; FALSE otherwise.
*/
public function isCommonReferenceTarget();
/**
* Gets an array of validation constraints.
*
* See \Drupal\Core\TypedData\DataDefinitionInterface::getConstraints() for
* details on how constraints are defined.
*
* @return array[]
* An array of validation constraint definitions, keyed by constraint name.
* Each constraint definition can be used for instantiating
* \Symfony\Component\Validator\Constraint objects.
*
* @see \Symfony\Component\Validator\Constraint
*/
public function getConstraints();
/**
* Sets the array of validation constraints for the FieldItemList.
*
* NOTE: This will overwrite any previously set constraints. In most cases
* ContentEntityTypeInterface::addConstraint() should be used instead.
* See \Drupal\Core\TypedData\DataDefinitionInterface::getConstraints() for
* details on how constraints are defined.
*
* @param array $constraints
* An array of validation constraint definitions, keyed by constraint name.
* Each constraint definition can be used for instantiating
* \Symfony\Component\Validator\Constraint objects.
*
* @return $this
*
* @see \Symfony\Component\Validator\Constraint
*/
public function setConstraints(array $constraints);
/**
* Adds a validation constraint.
*
* See \Drupal\Core\TypedData\DataDefinitionInterface::getConstraints() for
* details on how constraints are defined.
*
* @param string $constraint_name
* The name of the constraint to add, i.e. its plugin id.
* @param array|null $options
* The constraint options as required by the constraint plugin, or NULL.
*
* @return $this
*/
public function addConstraint($constraint_name, $options = NULL);
}

View file

@ -0,0 +1,41 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\EntityTypeListenerInterface.
*/
namespace Drupal\Core\Entity;
/**
* Defines an interface for reacting to entity type creation, deletion, and updates.
*/
interface EntityTypeListenerInterface {
/**
* Reacts to the creation of the entity type.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type being created.
*/
public function onEntityTypeCreate(EntityTypeInterface $entity_type);
/**
* Reacts to the update of the entity type.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The updated entity type definition.
* @param \Drupal\Core\Entity\EntityTypeInterface $original
* The original entity type definition.
*/
public function onEntityTypeUpdate(EntityTypeInterface $entity_type, EntityTypeInterface $original);
/**
* Reacts to the deletion of the entity type.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type being deleted.
*/
public function onEntityTypeDelete(EntityTypeInterface $entity_type);
}

View file

@ -0,0 +1,484 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\EntityViewBuilder.
*/
namespace Drupal\Core\Entity;
use Drupal\Core\Cache\Cache;
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 Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Base class for entity view builders.
*
* @ingroup entity_api
*/
class EntityViewBuilder extends EntityHandlerBase implements EntityHandlerInterface, EntityViewBuilderInterface {
/**
* The type of entities for which this view builder is instantiated.
*
* @var string
*/
protected $entityTypeId;
/**
* Information about the entity type.
*
* @var \Drupal\Core\Entity\EntityTypeInterface
*/
protected $entityType;
/**
* The entity manager service.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* The cache bin used to store the render cache.
*
* @var string
*/
protected $cacheBin = 'render';
/**
* The language manager.
*
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
*/
protected $languageManager;
/**
* The EntityViewDisplay objects created for individual field rendering.
*
* @see \Drupal\Core\Entity\EntityViewBuilder::getSingleFieldDisplay()
*
* @param \Drupal\Core\Entity\Display\EntityViewDisplayInterface[]
*/
protected $singleFieldDisplays;
/**
* Constructs a new EntityViewBuilder.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type definition.
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager service.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
*/
public function __construct(EntityTypeInterface $entity_type, EntityManagerInterface $entity_manager, LanguageManagerInterface $language_manager) {
$this->entityTypeId = $entity_type->id();
$this->entityType = $entity_type;
$this->entityManager = $entity_manager;
$this->languageManager = $language_manager;
}
/**
* {@inheritdoc}
*/
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
return new static(
$entity_type,
$container->get('entity.manager'),
$container->get('language_manager')
);
}
/**
* {@inheritdoc}
*/
public function view(EntityInterface $entity, $view_mode = 'full', $langcode = NULL) {
$build_list = $this->viewMultiple(array($entity), $view_mode, $langcode);
// The default ::buildMultiple() #pre_render callback won't run, because we
// extract a child element of the default renderable array. Thus we must
// assign an alternative #pre_render callback that applies the necessary
// transformations and then still calls ::buildMultiple().
$build = $build_list[0];
$build['#pre_render'][] = array($this, 'build');
return $build;
}
/**
* {@inheritdoc}
*/
public function viewMultiple(array $entities = array(), $view_mode = 'full', $langcode = NULL) {
if (!isset($langcode)) {
$langcode = $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)->getId();
}
$build_list = array(
'#sorted' => TRUE,
'#pre_render' => array(array($this, 'buildMultiple')),
'#langcode' => $langcode,
);
$weight = 0;
foreach ($entities as $key => $entity) {
// Ensure that from now on we are dealing with the proper translation
// object.
$entity = $this->entityManager->getTranslationFromContext($entity, $langcode);
// Set build defaults.
$build_list[$key] = $this->getBuildDefaults($entity, $view_mode, $langcode);
$entityType = $this->entityTypeId;
$this->moduleHandler()->alter(array($entityType . '_build_defaults', 'entity_build_defaults'), $build_list[$key], $entity, $view_mode, $langcode);
$build_list[$key]['#weight'] = $weight++;
}
return $build_list;
}
/**
* Provides entity-specific defaults to the build process.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* 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) {
// Allow modules to change the view mode.
$context = array('langcode' => $langcode);
$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()),
'contexts' => $entity->getCacheContexts(),
'max-age' => $entity->getCacheMaxAge(),
),
);
// Cache the rendered output if permitted by the view mode and global entity
// type configuration.
if ($this->isViewModeCacheable($view_mode) && !$entity->isNew() && $entity->isDefaultRevision() && $this->entityType->isRenderCacheable()) {
$build['#cache'] += array(
'keys' => array(
'entity_view',
$this->entityTypeId,
$entity->id(),
$view_mode,
),
'bin' => $this->cacheBin,
);
if ($entity instanceof TranslatableInterface && count($entity->getTranslationLanguages()) > 1) {
$build['#cache']['keys'][] = $langcode;
}
}
return $build;
}
/**
* Builds an entity's view; augments entity defaults.
*
* This function is assigned as a #pre_render callback in ::view().
*
* It transforms the renderable array for a single entity to the same
* structure as if we were rendering multiple entities, and then calls the
* default ::buildMultiple() #pre_render callback.
*
* @param array $build
* A renderable array containing build information and context for an entity
* view.
*
* @return array
* The updated renderable array.
*
* @see drupal_render()
*/
public function build(array $build) {
$build_list = array(
'#langcode' => $build['#langcode'],
);
$build_list[] = $build;
$build_list = $this->buildMultiple($build_list);
return $build_list[0];
}
/**
* Builds multiple entities' views; augments entity defaults.
*
* This function is assigned as a #pre_render callback in ::viewMultiple().
*
* By delaying the building of an entity until the #pre_render processing in
* drupal_render(), the processing cost of assembling an entity's renderable
* array is saved on cache-hit requests.
*
* @param array $build_list
* A renderable array containing build information and context for an
* entity view.
*
* @return array
* The updated renderable array.
*
* @see drupal_render()
*/
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";
// Find the keys for the ContentEntities in the build; Store entities for
// rendering by view_mode.
$children = Element::children($build_list);
foreach ($children as $key) {
if (isset($build_list[$key][$entity_type_key])) {
$entity = $build_list[$key][$entity_type_key];
if ($entity instanceof FieldableEntityInterface) {
$view_modes[$build_list[$key]['#view_mode']][$key] = $entity;
}
}
}
// 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);
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->alterBuild($build_list[$key], $entity, $display, $view_mode, $langcode);
// Assign the weights configured in the display.
// @todo: Once https://www.drupal.org/node/1875974 provides the missing
// API, only do it for 'extra fields', since other components have
// been taken care of in EntityViewDisplay::buildMultiple().
foreach ($display->getComponents() as $name => $options) {
if (isset($build_list[$key][$name])) {
$build_list[$key][$name]['#weight'] = $options['weight'];
}
}
// Allow modules to modify the render array.
$this->moduleHandler()->alter(array($view_hook, 'entity_view'), $build_list[$key], $entity, $display);
}
}
return $build_list;
}
/**
* {@inheritdoc}
*/
public function buildComponents(array &$build, array $entities, array $displays, $view_mode, $langcode = NULL) {
$entities_by_bundle = array();
foreach ($entities as $id => $entity) {
// Initialize the field item attributes for the fields being displayed.
// The entity can include fields that are not displayed, and the display
// can include components that are not fields, so we want to act on the
// intersection. However, the entity can have many more fields than are
// displayed, so we avoid the cost of calling $entity->getProperties()
// by iterating the intersection as follows.
foreach ($displays[$entity->bundle()]->getComponents() as $name => $options) {
if ($entity->hasField($name)) {
foreach ($entity->get($name) as $item) {
$item->_attributes = array();
}
}
}
// Group the entities by bundle.
$entities_by_bundle[$entity->bundle()][$id] = $entity;
}
// Invoke hook_entity_prepare_view().
$this->moduleHandler()->invokeAll('entity_prepare_view', array($this->entityTypeId, $entities, $displays, $view_mode));
// Let the displays build their render arrays.
foreach ($entities_by_bundle as $bundle => $bundle_entities) {
$display_build = $displays[$bundle]->buildMultiple($bundle_entities);
foreach ($bundle_entities as $id => $entity) {
$build[$id] += $display_build[$id];
}
}
}
/**
* Specific per-entity building.
*
* @param array $build
* The render array that is being created.
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity to be prepared.
* @param \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display
* The entity view display holding the display options configured for the
* 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) { }
/**
* {@inheritdoc}
*/
public function getCacheTags() {
return array($this->entityTypeId . '_view');
}
/**
* {@inheritdoc}
*/
public function resetCache(array $entities = NULL) {
// If no set of specific entities is provided, invalidate the entity view
// builder's cache tag. This will invalidate all entities rendered by this
// view builder.
// Otherwise, if a set of specific entities is provided, invalidate those
// specific entities only, plus their list cache tags, because any lists in
// which these entities are rendered, must be invalidated as well. However,
// even in this case, we might invalidate more cache items than necessary.
// When we have a way to invalidate only those cache items that have both
// the individual entity's cache tag and the view builder's cache tag, we'll
// be able to optimize this further.
if (isset($entities)) {
$tags = [];
foreach ($entities as $entity) {
$tags = Cache::mergeTags($tags, $entity->getCacheTags(), $entity->getEntityType()->getListCacheTags());
}
Cache::invalidateTags($tags);
}
else {
Cache::invalidateTags($this->getCacheTags());
}
}
/**
* Determines whether the view mode is cacheable.
*
* @param string $view_mode
* Name of the view mode that should be rendered.
*
* @return bool
* TRUE if the view mode can be cached, FALSE otherwise.
*/
protected function isViewModeCacheable($view_mode) {
if ($view_mode == 'default') {
// The 'default' is not an actual view mode.
return TRUE;
}
$view_modes_info = $this->entityManager->getViewModes($this->entityTypeId);
return !empty($view_modes_info[$view_mode]['cache']);
}
/**
* {@inheritdoc}
*/
public function viewField(FieldItemListInterface $items, $display_options = array()) {
$entity = $items->getEntity();
$field_name = $items->getFieldDefinition()->getName();
$display = $this->getSingleFieldDisplay($entity, $field_name, $display_options);
$output = array();
$build = $display->build($entity);
if (isset($build[$field_name])) {
$output = $build[$field_name];
}
return $output;
}
/**
* {@inheritdoc}
*/
public function viewFieldItem(FieldItemInterface $item, $display = array()) {
$entity = $item->getEntity();
$field_name = $item->getFieldDefinition()->getName();
// Clone the entity since we are going to modify field values.
$clone = clone $entity;
// Push the item as the single value for the field, and defer to viewField()
// to build the render array for the whole list.
$clone->{$field_name}->setValue(array($item->getValue()));
$elements = $this->viewField($clone->{$field_name}, $display);
// Extract the part of the render array we need.
$output = isset($elements[0]) ? $elements[0] : array();
if (isset($elements['#access'])) {
$output['#access'] = $elements['#access'];
}
return $output;
}
/**
* Gets an EntityViewDisplay for rendering an individual field.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity.
* @param string $field_name
* The field name.
* @param string|array $display_options
* The display options passed to the viewField() method.
*
* @return \Drupal\Core\Entity\Display\EntityViewDisplayInterface
*/
protected function getSingleFieldDisplay($entity, $field_name, $display_options) {
if (is_string($display_options)) {
// View mode: use the Display configured for the view mode.
$view_mode = $display_options;
$display = EntityViewDisplay::collectRenderDisplay($entity, $view_mode);
// Hide all fields except the current one.
foreach (array_keys($entity->getFieldDefinitions()) as $name) {
if ($name != $field_name) {
$display->removeComponent($name);
}
}
}
else {
// Array of custom display options: use a runtime Display for the
// '_custom' view mode. Persist the displays created, to reduce the number
// of objects (displays and formatter plugins) created when rendering a
// series of fields individually for cases such as views tables.
$entity_type_id = $entity->getEntityTypeId();
$bundle = $entity->bundle();
$key = $entity_type_id . ':' . $bundle . ':' . $field_name . ':' . crc32(serialize($display_options));
if (!isset($this->singleFieldDisplays[$key])) {
$this->singleFieldDisplays[$key] = EntityViewDisplay::create(array(
'targetEntityType' => $entity_type_id,
'bundle' => $bundle,
'status' => TRUE,
))->setComponent($field_name, $display_options);
}
$display = $this->singleFieldDisplays[$key];
}
return $display;
}
}

View file

@ -0,0 +1,162 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\EntityViewBuilderInterface.
*/
namespace Drupal\Core\Entity;
use Drupal\Core\Field\FieldItemInterface;
use Drupal\Core\Field\FieldItemListInterface;
/**
* Defines an interface for entity view builders.
*
* @ingroup entity_api
*/
interface EntityViewBuilderInterface {
/**
* Builds the component fields and properties of a set of entities.
*
* @param &$build
* The renderable array representing the entity content.
* @param \Drupal\Core\Entity\EntityInterface[] $entities
* The entities whose content is being built.
* @param \Drupal\Core\Entity\Display\EntityViewDisplayInterface[] $displays
* The array of entity view displays holding the display options
* 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);
/**
* Builds the render array for the provided entity.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity to render.
* @param string $view_mode
* (optional) The view mode that should be used to render the entity.
* @param string $langcode
* (optional) For which language the entity should be rendered, defaults to
* the current content language.
*
* @return array
* A render array for the entity.
*
* @throws \InvalidArgumentException
* Can be thrown when the set of parameters is inconsistent, like when
* trying to view a Comment and passing a Node which is not the one the
* comment belongs to, or not passing one, and having the comment node not
* be available for loading.
*/
public function view(EntityInterface $entity, $view_mode = 'full', $langcode = NULL);
/**
* Builds the render array for the provided entities.
*
* @param array $entities
* An array of entities implementing EntityInterface to view.
* @param string $view_mode
* (optional) The view mode that should be used to render the entity.
* @param string $langcode
* (optional) For which language the entity should be rendered, defaults to
* the current content language.
*
* @return
* A render array for the entities, indexed by the same keys as the
* entities array passed in $entities.
*
* @throws \InvalidArgumentException
* Can be thrown when the set of parameters is inconsistent, like when
* trying to view Comments and passing a Node which is not the one the
* comments belongs to, or not passing one, and having the comments node not
* be available for loading.
*/
public function viewMultiple(array $entities = array(), $view_mode = 'full', $langcode = NULL);
/**
* Resets the entity render cache.
*
* @param \Drupal\Core\Entity\EntityInterface[] $entities
* (optional) If specified, the cache is reset for the given entities only.
*/
public function resetCache(array $entities = NULL);
/**
* Builds a renderable array for the value of a single field in an entity.
*
* The resulting output is a fully themed field with label and multiple
* values.
*
* This function can be used by third-party modules that need to output an
* isolated field.
* - Do not use inside node (or any other entity) templates; use
* render($content[FIELD_NAME]) instead.
* - The FieldItemInterface::view() method can be used to output a single
* formatted field value, without label or wrapping field markup.
*
* The function takes care of invoking the prepare_view steps. It also
* respects field access permissions.
*
* @param \Drupal\Core\Field\FieldItemListInterface $items
* FieldItemList containing the values to be displayed.
* @param string|array $display_options
* Can be either:
* - The name of a view mode. The field will be displayed according to the
* display settings specified for this view mode in the $field
* definition for the field in the entity's bundle. If no display settings
* are found for the view mode, the settings for the 'default' view mode
* will be used.
* - An array of display options. The following key/value pairs are allowed:
* - label: (string) Position of the label. The default 'field' theme
* implementation supports the values 'inline', 'above' and 'hidden'.
* Defaults to 'above'.
* - type: (string) The formatter to use. Defaults to the
* 'default_formatter' for the field type. The default formatter will
* also be used if the requested formatter is not available.
* - settings: (array) Settings specific to the formatter. Defaults to the
* formatter's default settings.
* - weight: (float) The weight to assign to the renderable element.
* Defaults to 0.
*
* @return array
* A renderable array for the field values.
*
* @see \Drupal\Core\Entity\EntityViewBuilderInterface::viewFieldItem()
*/
public function viewField(FieldItemListInterface $items, $display_options = array());
/**
* Builds a renderable array for a single field item.
*
* @param \Drupal\Core\Field\FieldItemInterface $item
* FieldItem to be displayed.
* @param string|array $display_options
* Can be either the name of a view mode, or an array of display settings.
* See EntityViewBuilderInterface::viewField() for more information.
*
* @return array
* A renderable array for the field item.
*
* @see \Drupal\Core\Entity\EntityViewBuilderInterface::viewField()
*/
public function viewFieldItem(FieldItemInterface $item, $display_options = array());
/**
* The cache tag associated with this entity view builder.
*
* An entity view builder is instantiated on a per-entity type basis, so the
* cache tags are also per-entity type.
*
* @return array
* An array of cache tags.
*/
public function getCacheTags();
}

View file

@ -0,0 +1,15 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\EntityViewModeInterface.
*/
namespace Drupal\Core\Entity;
/**
* Provides an interface defining an entity view mode entity type.
*/
interface EntityViewModeInterface extends EntityDisplayModeInterface {
}

View file

@ -0,0 +1,28 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\EntityWithPluginCollectionInterface.
*/
namespace Drupal\Core\Entity;
/**
* Provides an interface for an object using a plugin collection.
*
* @see \Drupal\Component\Plugin\LazyPluginCollection
*
* @ingroup plugin_api
*/
interface EntityWithPluginCollectionInterface extends EntityInterface {
/**
* Gets the plugin collections used by this entity.
*
* @return \Drupal\Component\Plugin\LazyPluginCollection[]
* An array of plugin collections, keyed by the property name they use to
* store their configuration.
*/
public function getPluginCollections();
}

View file

@ -0,0 +1,79 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\Event\BundleConfigImportValidate.
*/
namespace Drupal\Core\Entity\Event;
use Drupal\Core\Config\ConfigImporterEvent;
use Drupal\Core\Config\ConfigImportValidateEventSubscriberBase;
use Drupal\Core\Config\ConfigManagerInterface;
use Drupal\Core\Config\Entity\ConfigEntityStorage;
use Drupal\Core\Entity\EntityManagerInterface;
/**
* Entity config importer validation event subscriber.
*/
class BundleConfigImportValidate extends ConfigImportValidateEventSubscriberBase {
/**
* The config manager.
*
* @var \Drupal\Core\Config\ConfigManagerInterface
*/
protected $configManager;
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* Constructs the event subscriber.
*
* @param \Drupal\Core\Config\ConfigManagerInterface $config_manager
* The config manager
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
*/
public function __construct(ConfigManagerInterface $config_manager, EntityManagerInterface $entity_manager) {
$this->configManager = $config_manager;
$this->entityManager = $entity_manager;
}
/**
* Ensures bundles that will be deleted are not in use.
*
* @param \Drupal\Core\Config\ConfigImporterEvent $event
* The config import event.
*/
public function onConfigImporterValidate(ConfigImporterEvent $event) {
foreach ($event->getChangelist('delete') as $config_name) {
// Get the config entity type ID. This also ensure we are dealing with a
// configuration entity.
if ($entity_type_id = $this->configManager->getEntityTypeIdByName($config_name)) {
$entity_type = $this->entityManager->getDefinition($entity_type_id);
// Does this entity type define a bundle of another entity type.
if ($bundle_of = $entity_type->getBundleOf()) {
// Work out if there are entities with this bundle.
$bundle_of_entity_type = $this->entityManager->getDefinition($bundle_of);
$bundle_id = ConfigEntityStorage::getIDFromConfigName($config_name, $entity_type->getConfigPrefix());
$entity_query = $this->entityManager->getStorage($bundle_of)->getQuery();
$entity_ids = $entity_query->condition($bundle_of_entity_type->getKey('bundle'), $bundle_id)
->accessCheck(FALSE)
->range(0, 1)
->execute();
if (!empty($entity_ids)) {
$entity = $this->entityManager->getStorage($entity_type_id)->load($bundle_id);
$event->getConfigImporter()->logError($this->t('Entities exist of type %entity_type and %bundle_label %bundle. These entities need to be deleted before importing.', array('%entity_type' => $bundle_of_entity_type->getLabel(), '%bundle_label' => $bundle_of_entity_type->getBundleLabel(), '%bundle' => $entity->label())));
}
}
}
}
}
}

View file

@ -0,0 +1,28 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\Exception\AmbiguousEntityClassException.
*/
namespace Drupal\Core\Entity\Exception;
/**
* Exception thrown if multiple entity types exist for an entity class.
*
* @see hook_entity_info_alter()
*/
class AmbiguousEntityClassException extends \Exception {
/**
* Constructs an AmbiguousEntityClassException.
*
* @param string $class
* The entity parent class.
*/
public function __construct($class) {
$message = sprintf('Multiple entity types found for %s.', $class);
parent::__construct($message);
}
}

View file

@ -0,0 +1,13 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\Exception\EntityTypeIdLengthException.
*/
namespace Drupal\Core\Entity\Exception;
/**
* Defines an exception thrown when an entity ID is too long.
*/
class EntityTypeIdLengthException extends \Exception { }

View file

@ -0,0 +1,16 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\Exception\FieldStorageDefinitionUpdateForbiddenException.
*/
namespace Drupal\Core\Entity\Exception;
use Drupal\Core\Entity\EntityStorageException;
/**
* Exception thrown when a storage definition update is forbidden.
*/
class FieldStorageDefinitionUpdateForbiddenException extends EntityStorageException {
}

View file

@ -0,0 +1,30 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\Exception\NoCorrespondingEntityClassException.
*/
namespace Drupal\Core\Entity\Exception;
/**
* Exception thrown if an entity type is not represented by a class.
*
* This might occur by calling a static method on an abstract class.
*
* @see \Drupal\Core\Entity\Entity::getEntityTypeFromStaticClass()
*/
class NoCorrespondingEntityClassException extends \Exception {
/**
* Constructs an NoCorrespondingEntityClassException.
*
* @param string $class
* The class which does not correspond to an entity type.
*/
public function __construct($class) {
$message = sprintf('The %s class does not correspond to an entity type.', $class);
parent::__construct($message);
}
}

View file

@ -0,0 +1,15 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\Exception\UndefinedLinkTemplateException.
*/
namespace Drupal\Core\Entity\Exception;
/**
* Defines an exception class for undefined link templates.
*/
class UndefinedLinkTemplateException extends \RuntimeException {
}

View file

@ -0,0 +1,215 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\FieldableEntityInterface.
*/
namespace Drupal\Core\Entity;
/**
* Interface for entities having fields.
*
* This interface builds upon the general interfaces provided by the typed data
* API, while extending them with entity-specific additions. I.e., fieldable
* entities implement the ComplexDataInterface among others, thus it is complex
* data containing fields as its data properties. The contained fields have to
* implement \Drupal\Core\Field\FieldItemListInterface, which builds upon typed
* data interfaces as well.
*
* When implementing this interface which extends Traversable, make sure to list
* IteratorAggregate or Iterator before this interface in the implements clause.
*
* @see \Drupal\Core\TypedData\TypedDataManager
* @see \Drupal\Core\Field\FieldItemListInterface
*
* @ingroup entity_api
*/
interface FieldableEntityInterface extends EntityInterface {
/**
* Provides base field definitions for an entity type.
*
* Implementations typically use the class
* \Drupal\Core\Field\BaseFieldDefinition for creating the field definitions;
* for example a 'name' field could be defined as the following:
* @code
* $fields['name'] = BaseFieldDefinition::create('string')
* ->setLabel(t('Name'));
* @endcode
*
* By definition, base fields are fields that exist for every bundle. To
* provide definitions for fields that should only exist on some bundles, use
* \Drupal\Core\Entity\FieldableEntityInterface::bundleFieldDefinitions().
*
* The definitions returned by this function can be overridden for all
* bundles by hook_entity_base_field_info_alter() or overridden on a
* per-bundle basis via 'base_field_override' configuration entities.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type definition. Useful when a single class is used for multiple,
* possibly dynamic entity types.
*
* @return \Drupal\Core\Field\FieldDefinitionInterface[]
* An array of base field definitions for the entity type, keyed by field
* name.
*
* @see \Drupal\Core\Entity\EntityManagerInterface::getFieldDefinitions()
* @see \Drupal\Core\Entity\FieldableEntityInterface::bundleFieldDefinitions()
*/
public static function baseFieldDefinitions(EntityTypeInterface $entity_type);
/**
* Provides field definitions for a specific bundle.
*
* This function can return definitions both for bundle fields (fields that
* are not defined in $base_field_definitions, and therefore might not exist
* on some bundles) as well as bundle-specific overrides of base fields
* (fields that are defined in $base_field_definitions, and therefore exist
* for all bundles). However, bundle-specific base field overrides can also
* be provided by 'base_field_override' configuration entities, and that is
* the recommended approach except in cases where an entity type needs to
* provide a bundle-specific base field override that is decoupled from
* configuration. Note that for most entity types, the bundles themselves are
* derived from configuration (e.g., 'node' bundles are managed via
* 'node_type' configuration entities), so decoupling bundle-specific base
* field overrides from configuration only makes sense for entity types that
* also decouple their bundles from configuration. In cases where both this
* function returns a bundle-specific override of a base field and a
* 'base_field_override' configuration entity exists, the latter takes
* precedence.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type definition. Useful when a single class is used for multiple,
* possibly dynamic entity types.
* @param string $bundle
* The bundle.
* @param \Drupal\Core\Field\FieldDefinitionInterface[] $base_field_definitions
* The list of base field definitions.
*
* @return \Drupal\Core\Field\FieldDefinitionInterface[]
* An array of bundle field definitions, keyed by field name.
*
* @see \Drupal\Core\Entity\EntityManagerInterface::getFieldDefinitions()
* @see \Drupal\Core\Entity\FieldableEntityInterface::baseFieldDefinitions()
*
* @todo WARNING: This method will be changed in
* https://www.drupal.org/node/2346347.
*/
public static function bundleFieldDefinitions(EntityTypeInterface $entity_type, $bundle, array $base_field_definitions);
/**
* Determines whether the entity has a field with the given name.
*
* @param string $field_name
* The field name.
*
* @return bool
* TRUE if the entity has a field with the given name. FALSE otherwise.
*/
public function hasField($field_name);
/**
* Gets the definition of a contained field.
*
* @param string $name
* The name of the field.
*
* @return \Drupal\Core\Field\FieldDefinitionInterface|null
* The definition of the field or null if the field does not exist.
*/
public function getFieldDefinition($name);
/**
* Gets an array of field definitions of all contained fields.
*
* @return \Drupal\Core\Field\FieldDefinitionInterface[]
* An array of field definitions, keyed by field name.
*
* @see \Drupal\Core\Entity\EntityManagerInterface::getFieldDefinitions()
*/
public function getFieldDefinitions();
/**
* Gets an array of all field values.
*
* Gets an array of plain field values, including only non-computed values.
* Note that the structure varies by entity type and bundle.
*
* @return array
* An array of field values, keyed by field name.
*/
public function toArray();
/**
* Gets a field item list.
*
* @param string $field_name
* The name of the field to get; e.g., 'title' or 'name'.
*
* @throws \InvalidArgumentException
* If an invalid field name is given.
*
* @return \Drupal\Core\Field\FieldItemListInterface
* The field item list, containing the field items.
*/
public function get($field_name);
/**
* Sets a field value.
*
* @param string $field_name
* The name of the field to set; e.g., 'title' or 'name'.
* @param mixed $value
* The value to set, or NULL to unset the field.
* @param bool $notify
* (optional) Whether to notify the entity of the change. Defaults to
* TRUE. If the update stems from the entity, set it to FALSE to avoid
* being notified again.
*
* @throws \InvalidArgumentException
* If the specified field does not exist.
*
* @return $this
*/
public function set($field_name, $value, $notify = TRUE);
/**
* Gets an array of field item lists.
*
* @param bool $include_computed
* If set to TRUE, computed fields are included. Defaults to TRUE.
*
* @return \Drupal\Core\Field\FieldItemListInterface[]
* An array of field item lists implementing, keyed by field name.
*/
public function getFields($include_computed = TRUE);
/**
* Reacts to changes to a field.
*
* Note that this is invoked after any changes have been applied.
*
* @param string $field_name
* The name of the field which is changed.
*
* @throws \InvalidArgumentException
* When trying to assign a value to the language field that matches an
* existing translation.
* @throws \LogicException
* When trying to change:
* - The language of a translation.
* - The value of the flag identifying the default translation object.
*/
public function onChange($field_name);
/**
* Validates the currently set values.
*
* @return \Drupal\Core\Entity\EntityConstraintViolationListInterface
* A list of constraint violations. If the list is empty, validation
* succeeded.
*/
public function validate();
}

View file

@ -0,0 +1,32 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\FieldableEntityStorageInterface.
*/
namespace Drupal\Core\Entity;
/**
* A storage that supports entity types with field definitions.
*/
interface FieldableEntityStorageInterface extends EntityStorageInterface {
/**
* Determines the number of entities with values for a given field.
*
* @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
* The field for which to count data records.
* @param bool $as_bool
* (Optional) Optimises the query for checking whether there are any records
* or not. Defaults to FALSE.
*
* @return bool|int
* The number of entities. If $as_bool parameter is TRUE then the
* value will either be TRUE or FALSE.
*
* @see \Drupal\Core\Entity\FieldableEntityStorageInterface::purgeFieldData()
*/
public function countFieldData($storage_definition, $as_bool = FALSE);
}

View file

@ -0,0 +1,84 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\HtmlEntityFormController.
*/
namespace Drupal\Core\Entity;
use Drupal\Core\Controller\ControllerResolverInterface;
use Drupal\Core\Controller\FormController;
use Drupal\Core\Form\FormBuilderInterface;
use Drupal\Core\Routing\RouteMatchInterface;
/**
* Wrapping controller for entity forms that serve as the main page body.
*/
class HtmlEntityFormController extends FormController {
/**
* The entity manager service.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* Constructs a new \Drupal\Core\Routing\Enhancer\FormEnhancer object.
*
* @param \Drupal\Core\Controller\ControllerResolverInterface $resolver
* The controller resolver.
* @param \Drupal\Core\Form\FormBuilderInterface $form_builder
* The form builder.
* @param \Drupal\Core\Entity\EntityManagerInterface $manager
* The entity manager.
*/
public function __construct(ControllerResolverInterface $resolver, FormBuilderInterface $form_builder, EntityManagerInterface $manager) {
parent::__construct($resolver, $form_builder);
$this->entityManager = $manager;
}
/**
* {@inheritdoc}
*/
protected function getFormArgument(RouteMatchInterface $route_match) {
return $route_match->getRouteObject()->getDefault('_entity_form');
}
/**
* {@inheritdoc}
*
* Instead of a class name or service ID, $form_arg will be a string
* representing the entity and operation being performed.
* Consider the following route:
* @code
* path: '/foo/{node}/bar'
* defaults:
* _entity_form: 'node.edit'
* @endcode
* This means that the edit form for the node entity will used.
* If the entity type has a default form, only the name of the
* entity {param} needs to be passed:
* @code
* path: '/foo/{node}/baz'
* defaults:
* _entity_form: 'node'
* @endcode
*/
protected function getFormObject(RouteMatchInterface $route_match, $form_arg) {
// If no operation is provided, use 'default'.
$form_arg .= '.default';
list ($entity_type_id, $operation) = explode('.', $form_arg);
$form_object = $this->entityManager->getFormObject($entity_type_id, $operation);
// Allow the entity form to determine the entity object from a given route
// match.
$entity = $form_object->getEntityFromRouteMatch($route_match, $entity_type_id);
$form_object->setEntity($entity);
return $form_object;
}
}

View file

@ -0,0 +1,209 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\KeyValueStore\KeyValueEntityStorage.
*/
namespace Drupal\Core\Entity\KeyValueStore;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Uuid\UuidInterface;
use Drupal\Core\Config\Entity\Exception\ConfigEntityIdLengthException;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityMalformedException;
use Drupal\Core\Entity\EntityStorageBase;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\KeyValueStore\KeyValueStoreInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a key value backend for entities.
*
* @todo Entities that depend on auto-incrementing serial IDs need to explicitly
* provide an ID until a generic wrapper around the functionality provided by
* \Drupal\Core\Database\Connection::nextId() is added and used.
* @todo Revisions are currently not supported.
*/
class KeyValueEntityStorage extends EntityStorageBase {
/**
* Length limit of the entity ID.
*/
const MAX_ID_LENGTH = 128;
/**
* The key value store.
*
* @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface
*/
protected $keyValueStore;
/**
* The UUID service.
*
* @var \Drupal\Component\Uuid\UuidInterface
*/
protected $uuidService;
/**
* The language manager.
*
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
/**
* Constructs a new KeyValueEntityStorage.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type.
* @param \Drupal\Core\KeyValueStore\KeyValueStoreInterface $key_value_store
* The key value store.
* @param \Drupal\Component\Uuid\UuidInterface $uuid_service
* The UUID service.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
*/
public function __construct(EntityTypeInterface $entity_type, KeyValueStoreInterface $key_value_store, UuidInterface $uuid_service, LanguageManagerInterface $language_manager) {
parent::__construct($entity_type);
$this->keyValueStore = $key_value_store;
$this->uuidService = $uuid_service;
$this->languageManager = $language_manager;
// Check if the entity type supports UUIDs.
$this->uuidKey = $this->entityType->getKey('uuid');
}
/**
* {@inheritdoc}
*/
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
return new static(
$entity_type,
$container->get('keyvalue')->get('entity_storage__' . $entity_type->id()),
$container->get('uuid'),
$container->get('language_manager')
);
}
/**
* {@inheritdoc}
*/
public function doCreate(array $values = array()) {
// Set default language to site default if not provided.
$values += array($this->getEntityType()->getKey('langcode') => $this->languageManager->getDefaultLanguage()->getId());
$entity = new $this->entityClass($values, $this->entityTypeId);
// @todo This is handled by ContentEntityStorageBase, which assumes
// FieldableEntityInterface. The current approach in
// https://www.drupal.org/node/1867228 improves this but does not solve it
// completely.
if ($entity instanceof FieldableEntityInterface) {
foreach ($entity as $name => $field) {
if (isset($values[$name])) {
$entity->$name = $values[$name];
}
elseif (!array_key_exists($name, $values)) {
$entity->get($name)->applyDefaultValue();
}
unset($values[$name]);
}
}
return $entity;
}
/**
* {@inheritdoc}
*/
public function doLoadMultiple(array $ids = NULL) {
if (empty($ids)) {
$entities = $this->keyValueStore->getAll();
}
else {
$entities = $this->keyValueStore->getMultiple($ids);
}
return $this->mapFromStorageRecords($entities);
}
/**
* {@inheritdoc}
*/
public function loadRevision($revision_id) {
return NULL;
}
/**
* {@inheritdoc}
*/
public function deleteRevision($revision_id) {
return NULL;
}
/**
* {@inheritdoc}
*/
public function doDelete($entities) {
$entity_ids = array();
foreach ($entities as $entity) {
$entity_ids[] = $entity->id();
}
$this->keyValueStore->deleteMultiple($entity_ids);
}
/**
* {@inheritdoc}
*/
public function save(EntityInterface $entity) {
$id = $entity->id();
if ($id === NULL || $id === '') {
throw new EntityMalformedException('The entity does not have an ID.');
}
// Check the entity ID length.
// @todo This is not config-specific, but serial IDs will likely never hit
// this limit. Consider renaming the exception class.
if (strlen($entity->id()) > static::MAX_ID_LENGTH) {
throw new ConfigEntityIdLengthException(SafeMarkup::format('Entity ID @id exceeds maximum allowed length of @length characters.', array(
'@id' => $entity->id(),
'@length' => static::MAX_ID_LENGTH,
)));
}
return parent::save($entity);
}
/**
* {@inheritdoc}
*/
protected function doSave($id, EntityInterface $entity) {
$is_new = $entity->isNew();
// Save the entity data in the key value store.
$this->keyValueStore->set($entity->id(), $entity->toArray());
// If this is a rename, delete the original entity.
if ($this->has($id, $entity) && $id !== $entity->id()) {
$this->keyValueStore->delete($id);
}
return $is_new ? SAVED_NEW : SAVED_UPDATED;
}
/**
* {@inheritdoc}
*/
protected function has($id, EntityInterface $entity) {
return $this->keyValueStore->has($id);
}
/**
* {@inheritdoc}
*/
protected function getQueryServiceName() {
return 'entity.query.keyvalue';
}
}

View file

@ -0,0 +1,17 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\KeyValueStore\Query\Condition.
*/
namespace Drupal\Core\Entity\KeyValueStore\Query;
use Drupal\Core\Config\Entity\Query\Condition as ConditionParent;
/**
* Defines the condition class for the key value entity query.
*/
class Condition extends ConditionParent {
}

View file

@ -0,0 +1,78 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\KeyValueStore\Query\Query.
*/
namespace Drupal\Core\Entity\KeyValueStore\Query;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\Query\QueryBase;
use Drupal\Core\KeyValueStore\KeyValueFactoryInterface;
/**
* Defines the entity query for entities stored in a key value backend.
*/
class Query extends QueryBase {
/**
* The key value factory.
*
* @var \Drupal\Core\KeyValueStore\KeyValueFactoryInterface
*/
protected $keyValueFactory;
/**
* Constructs a new Query.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type.
* @param string $conjunction
* - AND: all of the conditions on the query need to match.
* - OR: at least one of the conditions on the query need to match.
* @param array $namespaces
* List of potential namespaces of the classes belonging to this query.
* @param \Drupal\Core\KeyValueStore\KeyValueFactoryInterface $key_value_factory
* The key value factory.
*/
public function __construct(EntityTypeInterface $entity_type, $conjunction, array $namespaces, KeyValueFactoryInterface $key_value_factory) {
parent::__construct($entity_type, $conjunction, $namespaces);
$this->keyValueFactory = $key_value_factory;
}
/**
* {@inheritdoc}
*/
public function execute() {
// Load the relevant records.
$records = $this->keyValueFactory->get('entity_storage__' . $this->entityTypeId)->getAll();
// Apply conditions.
$result = $this->condition->compile($records);
// Apply sort settings.
foreach ($this->sort as $sort) {
$direction = $sort['direction'] == 'ASC' ? -1 : 1;
$field = $sort['field'];
uasort($result, function($a, $b) use ($field, $direction) {
return ($a[$field] <= $b[$field]) ? $direction : -$direction;
});
}
// Let the pager do its work.
$this->initializePager();
if ($this->range) {
$result = array_slice($result, $this->range['start'], $this->range['length'], TRUE);
}
if ($this->count) {
return count($result);
}
// Create the expected structure of entity_id => entity_id.
$entity_ids = array_keys($result);
return array_combine($entity_ids, $entity_ids);
}
}

View file

@ -0,0 +1,57 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\KeyValueStore\Query\QueryFactory.
*/
namespace Drupal\Core\Entity\KeyValueStore\Query;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\Query\QueryException;
use Drupal\Core\Entity\Query\QueryFactoryInterface;
use Drupal\Core\KeyValueStore\KeyValueFactoryInterface;
/**
* Provides a factory for creating the key value entity query.
*/
class QueryFactory implements QueryFactoryInterface {
/**
* The key value factory.
*
* @var \Drupal\Core\KeyValueStore\KeyValueFactoryInterface
*/
protected $keyValueFactory;
/**
* The namespace of this class, the parent class etc.
*
* @var array
*/
protected $namespaces;
/**
* Constructs a QueryFactory object.
*
*/
public function __construct(KeyValueFactoryInterface $key_value_factory) {
$this->keyValueFactory = $key_value_factory;
$this->namespaces = Query::getNamespaces($this);
}
/**
* {@inheritdoc}
*/
public function get(EntityTypeInterface $entity_type, $conjunction) {
return new Query($entity_type, $conjunction, $this->namespaces, $this->keyValueFactory);
}
/**
* {@inheritdoc}
*/
public function getAggregate(EntityTypeInterface $entity_type, $conjunction) {
throw new QueryException('Aggregation over key-value entity storage is not supported');
}
}

View file

@ -0,0 +1,101 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\Plugin\DataType\Deriver\EntityDeriver.
*/
namespace Drupal\Core\Entity\Plugin\DataType\Deriver;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides data type plugins for each existing entity type and bundle.
*/
class EntityDeriver implements ContainerDeriverInterface {
/**
* List of derivative definitions.
*
* @var array
*/
protected $derivatives = array();
/**
* The base plugin ID this derivative is for.
*
* @var string
*/
protected $basePluginId;
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* Constructs an EntityDeriver object.
*
* @param string $base_plugin_id
* The base plugin ID.
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
*/
public function __construct($base_plugin_id, EntityManagerInterface $entity_manager) {
$this->basePluginId = $base_plugin_id;
$this->entityManager = $entity_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, $base_plugin_id) {
return new static(
$base_plugin_id,
$container->get('entity.manager')
);
}
/**
* {@inheritdoc}
*/
public function getDerivativeDefinition($derivative_id, $base_plugin_definition) {
if (!empty($this->derivatives) && !empty($this->derivatives[$derivative_id])) {
return $this->derivatives[$derivative_id];
}
$this->getDerivativeDefinitions($base_plugin_definition);
if (isset($this->derivatives[$derivative_id])) {
return $this->derivatives[$derivative_id];
}
}
/**
* {@inheritdoc}
*/
public function getDerivativeDefinitions($base_plugin_definition) {
// Also keep the 'entity' defined as is.
$this->derivatives[''] = $base_plugin_definition;
// Add definitions for each entity type and bundle.
foreach ($this->entityManager->getDefinitions() as $entity_type_id => $entity_type) {
$this->derivatives[$entity_type_id] = array(
'label' => $entity_type->getLabel(),
'constraints' => $entity_type->getConstraints(),
) + $base_plugin_definition;
// Incorporate the bundles as entity:$entity_type:$bundle, if any.
foreach (entity_get_bundles($entity_type_id) as $bundle => $bundle_info) {
if ($bundle !== $entity_type_id) {
$this->derivatives[$entity_type_id . ':' . $bundle] = array(
'label' => $bundle_info['label'],
'constraints' => $this->derivatives[$entity_type_id]['constraints']
) + $base_plugin_definition;
}
}
}
return $this->derivatives;
}
}

View file

@ -0,0 +1,186 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\Plugin\DataType\EntityAdapter.
*/
namespace Drupal\Core\Entity\Plugin\DataType;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\TypedData\EntityDataDefinition;
use Drupal\Core\TypedData\ComplexDataInterface;
use Drupal\Core\TypedData\Exception\MissingDataException;
use Drupal\Core\TypedData\TypedData;
/**
* Defines the "entity" data type.
*
* Instances of this class wrap entity objects and allow to deal with entities
* based upon the Typed Data API.
*
* In addition to the "entity" data type, this exposes derived
* "entity:$entity_type" and "entity:$entity_type:$bundle" data types.
*
* @DataType(
* id = "entity",
* label = @Translation("Entity"),
* description = @Translation("All kind of entities, e.g. nodes, comments or users."),
* deriver = "\Drupal\Core\Entity\Plugin\DataType\Deriver\EntityDeriver",
* definition_class = "\Drupal\Core\Entity\TypedData\EntityDataDefinition"
* )
*/
class EntityAdapter extends TypedData implements \IteratorAggregate, ComplexDataInterface {
/**
* The wrapped entity object.
*
* @var \Drupal\Core\Entity\EntityInterface|null
*/
protected $entity;
/**
* Creates an instance wrapping the given entity.
*
* @param \Drupal\Core\Entity\EntityInterface|null $entity
* The entity object to wrap.
*
* @return static
*/
public static function createFromEntity(EntityInterface $entity) {
$definition = EntityDataDefinition::create()
->setEntityTypeId($entity->getEntityTypeId())
->setBundles([ $entity->bundle() ]);
$instance = new static($definition);
$instance->setValue($entity);
return $instance;
}
/**
* {@inheritdoc}
*/
public function getValue() {
return $this->entity;
}
/**
* {@inheritdoc}
*/
public function setValue($entity, $notify = TRUE) {
$this->entity = $entity;
// Notify the parent of any changes.
if ($notify && isset($this->parent)) {
$this->parent->onChange($this->name);
}
}
/**
* {@inheritdoc}
*/
public function get($property_name) {
if (!isset($this->entity)) {
throw new MissingDataException(SafeMarkup::format('Unable to get property @name as no entity has been provided.', array('@name' => $property_name)));
}
if (!$this->entity instanceof FieldableEntityInterface) {
// @todo: Add support for config entities in
// https://www.drupal.org/node/1818574.
throw new \InvalidArgumentException(SafeMarkup::format('Unable to get unknown property @name.', array('@name' => $property_name)));
}
// This will throw an exception for unknown fields.
return $this->entity->get($property_name);
}
/**
* {@inheritdoc}
*/
public function set($property_name, $value, $notify = TRUE) {
if (!isset($this->entity)) {
throw new MissingDataException(SafeMarkup::format('Unable to set property @name as no entity has been provided.', array('@name' => $property_name)));
}
if (!$this->entity instanceof FieldableEntityInterface) {
// @todo: Add support for config entities in
// https://www.drupal.org/node/1818574.
throw new \InvalidArgumentException(SafeMarkup::format('Unable to set unknown property @name.', array('@name' => $property_name)));
}
// This will throw an exception for unknown fields.
$this->entity->set($property_name, $value, $notify);
return $this;
}
/**
* {@inheritdoc}
*/
public function getProperties($include_computed = FALSE) {
if (!isset($this->entity)) {
throw new MissingDataException(SafeMarkup::format('Unable to get properties as no entity has been provided.'));
}
if (!$this->entity instanceof FieldableEntityInterface) {
// @todo: Add support for config entities in
// https://www.drupal.org/node/1818574.
return array();
}
return $this->entity->getFields($include_computed);
}
/**
* {@inheritdoc}
*/
public function toArray() {
if (!isset($this->entity)) {
throw new MissingDataException(SafeMarkup::format('Unable to get property values as no entity has been provided.'));
}
return $this->entity->toArray();
}
/**
* {@inheritdoc}
*/
public function isEmpty() {
return !isset($this->entity);
}
/**
* {@inheritdoc}
*/
public function onChange($property_name) {
if (isset($this->entity) && $this->entity instanceof FieldableEntityInterface) {
// Let the entity know of any changes.
$this->entity->onChange($property_name);
}
}
/**
* {@inheritdoc}
*/
public function getDataDefinition() {
return $this->definition;
}
/**
* {@inheritdoc}
*/
public function getString() {
return isset($this->entity) ? $this->entity->label() : '';
}
/**
* {@inheritdoc}
*/
public function applyDefaultValue($notify = TRUE) {
// Apply the default value of all properties.
foreach ($this->getProperties() as $property) {
$property->applyDefaultValue(FALSE);
}
return $this;
}
/**
* {@inheritdoc}
*/
public function getIterator() {
return isset($this->entity) ? $this->entity->getIterator() : new \ArrayIterator([]);
}
}

View file

@ -0,0 +1,129 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\Plugin\DataType\EntityReference.
*/
namespace Drupal\Core\Entity\Plugin\DataType;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\TypedData\DataReferenceBase;
/**
* Defines an 'entity_reference' data type.
*
* This serves as 'entity' property of entity reference field items and gets
* its value set from the parent, i.e. LanguageItem.
*
* The plain value of this reference is the entity object, i.e. an instance of
* \Drupal\Core\Entity\EntityInterface. For setting the value the entity object
* or the entity ID may be passed.
*
* Note that the definition of the referenced entity's type is required, whereas
* defining referencable entity bundle(s) is optional. A reference defining the
* type and bundle of the referenced entity can be created as following:
* @code
* $definition = \Drupal\Core\Entity\EntityDefinition::create($entity_type)
* ->addConstraint('Bundle', $bundle);
* \Drupal\Core\TypedData\DataReferenceDefinition::create('entity')
* ->setTargetDefinition($definition);
* @endcode
*
* @DataType(
* id = "entity_reference",
* label = @Translation("Entity reference"),
* definition_class = "\Drupal\Core\TypedData\DataReferenceDefinition"
* )
*/
class EntityReference extends DataReferenceBase {
/**
* The entity ID.
*
* @var integer|string
*/
protected $id;
/**
* Gets the definition of the referenced entity.
*
* @return \Drupal\Core\Entity\TypedData\EntityDataDefinitionInterface
* The reference target's definition.
*/
public function getTargetDefinition() {
return $this->definition->getTargetDefinition();
}
/**
* Checks whether the target entity has not been saved yet.
*
* @return bool
* TRUE if the entity is new, FALSE otherwise.
*/
public function isTargetNew() {
// If only an ID is given, the reference cannot be a new entity.
return !isset($this->id) && isset($this->target) && $this->target->getValue()->isNew();
}
/**
* {@inheritdoc}
*/
public function getTarget() {
if (!isset($this->target) && isset($this->id)) {
// If we have a valid reference, return the entity's TypedData adapter.
$entity = entity_load($this->getTargetDefinition()->getEntityTypeId(), $this->id);
$this->target = isset($entity) ? $entity->getTypedData() : NULL;
}
return $this->target;
}
/**
* {@inheritdoc}
*/
public function getTargetIdentifier() {
if (isset($this->id)) {
return $this->id;
}
elseif ($entity = $this->getValue()) {
return $entity->id();
}
}
/**
* {@inheritdoc}
*/
public function setValue($value, $notify = TRUE) {
unset($this->target);
unset($this->id);
// Both the entity ID and the entity object may be passed as value. The
// reference may also be unset by passing NULL as value.
if (!isset($value)) {
$this->target = NULL;
}
elseif ($value instanceof EntityInterface) {
$this->target = $value->getTypedData();
}
elseif (!is_scalar($value) || $this->getTargetDefinition()->getEntityTypeId() === NULL) {
throw new \InvalidArgumentException('Value is not a valid entity.');
}
else {
$this->id = $value;
}
// Notify the parent of any changes.
if ($notify && isset($this->parent)) {
$this->parent->onChange($this->name);
}
}
/**
* {@inheritdoc}
*/
public function getString() {
if ($entity = $this->getValue()) {
return $entity->label();
}
return '';
}
}

View file

@ -0,0 +1,65 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\Plugin\Derivative\SelectionBase.
*/
namespace Drupal\Core\Entity\Plugin\Derivative;
use Drupal\Component\Plugin\Derivative\DeriverBase;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides derivative plugins 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\EntityReferenceSelection\SelectionInterface
* @see plugin_api
*/
class SelectionBase extends DeriverBase implements ContainerDeriverInterface {
/**
* The entity manager
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* Creates an SelectionBase object.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
*/
public function __construct(EntityManagerInterface $entity_manager) {
$this->entityManager = $entity_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, $base_plugin_id) {
return new static(
$container->get('entity.manager')
);
}
/**
* {@inheritdoc}
*/
public function getDerivativeDefinitions($base_plugin_definition) {
foreach ($this->entityManager->getDefinitions() as $entity_type_id => $entity_type) {
$this->derivatives[$entity_type_id] = $base_plugin_definition;
$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'];
}
return parent::getDerivativeDefinitions($base_plugin_definition);
}
}

View file

@ -0,0 +1,76 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\Plugin\EntityReferenceSelection\Broken.
*/
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;
/**
* Defines a fallback plugin for missing entity_reference selection plugins.
*
* @EntityReferenceSelection(
* id = "broken",
* label = @Translation("Broken/Missing")
* )
*/
class Broken implements SelectionInterface {
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form['selection_handler'] = array(
'#markup' => t('The selected selection handler is broken.'),
);
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) {
return array();
}
/**
* {@inheritdoc}
*/
public function countReferenceableEntities($match = NULL, $match_operator = 'CONTAINS') {
return 0;
}
/**
* {@inheritdoc}
*/
public function validateReferenceableEntities(array $ids) {
return array();
}
/**
* {@inheritdoc}
*/
public function validateAutocompleteInput($input, &$element, FormStateInterface $form_state, $form, $strict = TRUE) { }
/**
* {@inheritdoc}
*/
public function entityQueryAlter(SelectInterface $query) { }
}

View file

@ -0,0 +1,382 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\Plugin\EntityReferenceSelection\SelectionBase.
*/
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"
* )
*/
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;
}
}

View file

@ -0,0 +1,63 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\Plugin\Validation\Constraint\BundleConstraint.
*/
namespace Drupal\Core\Entity\Plugin\Validation\Constraint;
use Symfony\Component\Validator\Constraint;
/**
* Checks if a value is a valid entity type.
*
* @Constraint(
* id = "Bundle",
* label = @Translation("Bundle", context = "Validation"),
* type = { "entity", "entity_reference" }
* )
*/
class BundleConstraint extends Constraint {
/**
* The default violation message.
*
* @var string
*/
public $message = 'The entity must be of bundle %bundle.';
/**
* The bundle option.
*
* @var string|array
*/
public $bundle;
/**
* Gets the bundle option as array.
*
* @return array
*/
public function getBundleOption() {
// Support passing the bundle as string, but force it to be an array.
if (!is_array($this->bundle)) {
$this->bundle = array($this->bundle);
}
return $this->bundle;
}
/**
* Overrides Constraint::getDefaultOption().
*/
public function getDefaultOption() {
return 'bundle';
}
/**
* Overrides Constraint::getRequiredOptions().
*/
public function getRequiredOptions() {
return array('bundle');
}
}

View file

@ -0,0 +1,31 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\Plugin\Validation\Constraint\BundleConstraintValidator.
*/
namespace Drupal\Core\Entity\Plugin\Validation\Constraint;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
/**
* Validates the Bundle constraint.
*/
class BundleConstraintValidator extends ConstraintValidator {
/**
* {@inheritdoc}
*/
public function validate($entity, Constraint $constraint) {
if (!isset($entity)) {
return;
}
if (!in_array($entity->bundle(), $constraint->getBundleOption())) {
$this->context->addViolation($constraint->message, array('%bundle' => implode(', ', $constraint->getBundleOption())));
}
}
}

View file

@ -0,0 +1,30 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\Plugin\Validation\Constraint\CompositeConstraintBase.
*/
namespace Drupal\Core\Entity\Plugin\Validation\Constraint;
use Symfony\Component\Validator\Constraint;
/**
* Provides a base class for constraints validating multiple fields.
*
* The constraint must be defined on entity-level; i.e., added to the entity
* type.
*
* @see \Drupal\Core\Entity\EntityType::addConstraint
*/
abstract class CompositeConstraintBase extends Constraint {
/**
* An array of entity fields which should be passed to the validator.
*
* @return string[]
* An array of field names.
*/
abstract public function coversFields();
}

Some files were not shown because too many files have changed in this diff Show more