Drupal 8.0.0 beta 12. More info: https://www.drupal.org/node/2514176
This commit is contained in:
commit
9921556621
13277 changed files with 1459781 additions and 0 deletions
45
core/lib/Drupal/Core/Entity/Annotation/ConfigEntityType.php
Normal file
45
core/lib/Drupal/Core/Entity/Annotation/ConfigEntityType.php
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
45
core/lib/Drupal/Core/Entity/Annotation/ContentEntityType.php
Normal file
45
core/lib/Drupal/Core/Entity/Annotation/ContentEntityType.php
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
}
|
||||
66
core/lib/Drupal/Core/Entity/Annotation/EntityType.php
Normal file
66
core/lib/Drupal/Core/Entity/Annotation/EntityType.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
1095
core/lib/Drupal/Core/Entity/ContentEntityBase.php
Normal file
1095
core/lib/Drupal/Core/Entity/ContentEntityBase.php
Normal file
File diff suppressed because it is too large
Load diff
129
core/lib/Drupal/Core/Entity/ContentEntityConfirmFormBase.php
Normal file
129
core/lib/Drupal/Core/Entity/ContentEntityConfirmFormBase.php
Normal 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.
|
||||
}
|
||||
|
||||
}
|
||||
147
core/lib/Drupal/Core/Entity/ContentEntityDeleteForm.php
Normal file
147
core/lib/Drupal/Core/Entity/ContentEntityDeleteForm.php
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
261
core/lib/Drupal/Core/Entity/ContentEntityForm.php
Normal file
261
core/lib/Drupal/Core/Entity/ContentEntityForm.php
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
64
core/lib/Drupal/Core/Entity/ContentEntityFormInterface.php
Normal file
64
core/lib/Drupal/Core/Entity/ContentEntityFormInterface.php
Normal 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);
|
||||
|
||||
}
|
||||
61
core/lib/Drupal/Core/Entity/ContentEntityInterface.php
Normal file
61
core/lib/Drupal/Core/Entity/ContentEntityInterface.php
Normal 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();
|
||||
|
||||
}
|
||||
148
core/lib/Drupal/Core/Entity/ContentEntityNullStorage.php
Normal file
148
core/lib/Drupal/Core/Entity/ContentEntityNullStorage.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
261
core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php
Normal file
261
core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
33
core/lib/Drupal/Core/Entity/ContentEntityType.php
Normal file
33
core/lib/Drupal/Core/Entity/ContentEntityType.php
Normal 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';
|
||||
}
|
||||
|
||||
}
|
||||
14
core/lib/Drupal/Core/Entity/ContentEntityTypeInterface.php
Normal file
14
core/lib/Drupal/Core/Entity/ContentEntityTypeInterface.php
Normal 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 {
|
||||
}
|
||||
54
core/lib/Drupal/Core/Entity/ContentUninstallValidator.php
Normal file
54
core/lib/Drupal/Core/Entity/ContentUninstallValidator.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
113
core/lib/Drupal/Core/Entity/Controller/EntityViewController.php
Normal file
113
core/lib/Drupal/Core/Entity/Controller/EntityViewController.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
77
core/lib/Drupal/Core/Entity/DependencyTrait.php
Normal file
77
core/lib/Drupal/Core/Entity/DependencyTrait.php
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
136
core/lib/Drupal/Core/Entity/Display/EntityDisplayInterface.php
Normal file
136
core/lib/Drupal/Core/Entity/Display/EntityDisplayInterface.php
Normal 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);
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
}
|
||||
292
core/lib/Drupal/Core/Entity/Element/EntityAutocomplete.php
Normal file
292
core/lib/Drupal/Core/Entity/Element/EntityAutocomplete.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
150
core/lib/Drupal/Core/Entity/Enhancer/EntityRouteEnhancer.php
Normal file
150
core/lib/Drupal/Core/Entity/Enhancer/EntityRouteEnhancer.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
611
core/lib/Drupal/Core/Entity/Entity.php
Normal file
611
core/lib/Drupal/Core/Entity/Entity.php
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
339
core/lib/Drupal/Core/Entity/Entity/EntityFormDisplay.php
Normal file
339
core/lib/Drupal/Core/Entity/Entity/EntityFormDisplay.php
Normal 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)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
47
core/lib/Drupal/Core/Entity/Entity/EntityFormMode.php
Normal file
47
core/lib/Drupal/Core/Entity/Entity/EntityFormMode.php
Normal 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 {
|
||||
|
||||
}
|
||||
296
core/lib/Drupal/Core/Entity/Entity/EntityViewDisplay.php
Normal file
296
core/lib/Drupal/Core/Entity/Entity/EntityViewDisplay.php
Normal 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)
|
||||
);
|
||||
}
|
||||
}
|
||||
49
core/lib/Drupal/Core/Entity/Entity/EntityViewMode.php
Normal file
49
core/lib/Drupal/Core/Entity/Entity/EntityViewMode.php
Normal 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 {
|
||||
|
||||
}
|
||||
61
core/lib/Drupal/Core/Entity/EntityAccessCheck.php
Normal file
61
core/lib/Drupal/Core/Entity/EntityAccessCheck.php
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
346
core/lib/Drupal/Core/Entity/EntityAccessControlHandler.php
Normal file
346
core/lib/Drupal/Core/Entity/EntityAccessControlHandler.php
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
}
|
||||
89
core/lib/Drupal/Core/Entity/EntityAutocompleteMatcher.php
Normal file
89
core/lib/Drupal/Core/Entity/EntityAutocompleteMatcher.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
}
|
||||
40
core/lib/Drupal/Core/Entity/EntityChangedInterface.php
Normal file
40
core/lib/Drupal/Core/Entity/EntityChangedInterface.php
Normal 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();
|
||||
}
|
||||
31
core/lib/Drupal/Core/Entity/EntityChangedTrait.php
Normal file
31
core/lib/Drupal/Core/Entity/EntityChangedTrait.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
116
core/lib/Drupal/Core/Entity/EntityConfirmFormBase.php
Normal file
116
core/lib/Drupal/Core/Entity/EntityConfirmFormBase.php
Normal 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) {}
|
||||
|
||||
}
|
||||
215
core/lib/Drupal/Core/Entity/EntityConstraintViolationList.php
Normal file
215
core/lib/Drupal/Core/Entity/EntityConstraintViolationList.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
||||
}
|
||||
76
core/lib/Drupal/Core/Entity/EntityCreateAccessCheck.php
Normal file
76
core/lib/Drupal/Core/Entity/EntityCreateAccessCheck.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
254
core/lib/Drupal/Core/Entity/EntityDefinitionUpdateManager.php
Normal file
254
core/lib/Drupal/Core/Entity/EntityDefinitionUpdateManager.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
||||
}
|
||||
52
core/lib/Drupal/Core/Entity/EntityDeleteForm.php
Normal file
52
core/lib/Drupal/Core/Entity/EntityDeleteForm.php
Normal 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');
|
||||
}
|
||||
|
||||
}
|
||||
133
core/lib/Drupal/Core/Entity/EntityDeleteFormTrait.php
Normal file
133
core/lib/Drupal/Core/Entity/EntityDeleteFormTrait.php
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
485
core/lib/Drupal/Core/Entity/EntityDisplayBase.php
Normal file
485
core/lib/Drupal/Core/Entity/EntityDisplayBase.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
115
core/lib/Drupal/Core/Entity/EntityDisplayModeBase.php
Normal file
115
core/lib/Drupal/Core/Entity/EntityDisplayModeBase.php
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
34
core/lib/Drupal/Core/Entity/EntityDisplayModeInterface.php
Normal file
34
core/lib/Drupal/Core/Entity/EntityDisplayModeInterface.php
Normal 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);
|
||||
}
|
||||
|
|
@ -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';
|
||||
|
||||
}
|
||||
412
core/lib/Drupal/Core/Entity/EntityForm.php
Normal file
412
core/lib/Drupal/Core/Entity/EntityForm.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
56
core/lib/Drupal/Core/Entity/EntityFormBuilder.php
Normal file
56
core/lib/Drupal/Core/Entity/EntityFormBuilder.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
42
core/lib/Drupal/Core/Entity/EntityFormBuilderInterface.php
Normal file
42
core/lib/Drupal/Core/Entity/EntityFormBuilderInterface.php
Normal 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());
|
||||
|
||||
}
|
||||
156
core/lib/Drupal/Core/Entity/EntityFormInterface.php
Normal file
156
core/lib/Drupal/Core/Entity/EntityFormInterface.php
Normal 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);
|
||||
|
||||
}
|
||||
15
core/lib/Drupal/Core/Entity/EntityFormModeInterface.php
Normal file
15
core/lib/Drupal/Core/Entity/EntityFormModeInterface.php
Normal 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 {
|
||||
|
||||
}
|
||||
56
core/lib/Drupal/Core/Entity/EntityHandlerBase.php
Normal file
56
core/lib/Drupal/Core/Entity/EntityHandlerBase.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
38
core/lib/Drupal/Core/Entity/EntityHandlerInterface.php
Normal file
38
core/lib/Drupal/Core/Entity/EntityHandlerInterface.php
Normal 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);
|
||||
|
||||
}
|
||||
419
core/lib/Drupal/Core/Entity/EntityInterface.php
Normal file
419
core/lib/Drupal/Core/Entity/EntityInterface.php
Normal 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();
|
||||
|
||||
}
|
||||
246
core/lib/Drupal/Core/Entity/EntityListBuilder.php
Normal file
246
core/lib/Drupal/Core/Entity/EntityListBuilder.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
58
core/lib/Drupal/Core/Entity/EntityListBuilderInterface.php
Normal file
58
core/lib/Drupal/Core/Entity/EntityListBuilderInterface.php
Normal 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();
|
||||
|
||||
}
|
||||
13
core/lib/Drupal/Core/Entity/EntityMalformedException.php
Normal file
13
core/lib/Drupal/Core/Entity/EntityMalformedException.php
Normal 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 { }
|
||||
1443
core/lib/Drupal/Core/Entity/EntityManager.php
Normal file
1443
core/lib/Drupal/Core/Entity/EntityManager.php
Normal file
File diff suppressed because it is too large
Load diff
518
core/lib/Drupal/Core/Entity/EntityManagerInterface.php
Normal file
518
core/lib/Drupal/Core/Entity/EntityManagerInterface.php
Normal 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);
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
@ -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';
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
}
|
||||
231
core/lib/Drupal/Core/Entity/EntityResolverManager.php
Normal file
231
core/lib/Drupal/Core/Entity/EntityResolverManager.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
499
core/lib/Drupal/Core/Entity/EntityStorageBase.php
Normal file
499
core/lib/Drupal/Core/Entity/EntityStorageBase.php
Normal 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();
|
||||
|
||||
}
|
||||
13
core/lib/Drupal/Core/Entity/EntityStorageException.php
Normal file
13
core/lib/Drupal/Core/Entity/EntityStorageException.php
Normal 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 { }
|
||||
196
core/lib/Drupal/Core/Entity/EntityStorageInterface.php
Normal file
196
core/lib/Drupal/Core/Entity/EntityStorageInterface.php
Normal 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();
|
||||
|
||||
}
|
||||
773
core/lib/Drupal/Core/Entity/EntityType.php
Normal file
773
core/lib/Drupal/Core/Entity/EntityType.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
63
core/lib/Drupal/Core/Entity/EntityTypeEvent.php
Normal file
63
core/lib/Drupal/Core/Entity/EntityTypeEvent.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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) {
|
||||
}
|
||||
|
||||
}
|
||||
69
core/lib/Drupal/Core/Entity/EntityTypeEvents.php
Normal file
69
core/lib/Drupal/Core/Entity/EntityTypeEvents.php
Normal 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';
|
||||
|
||||
}
|
||||
735
core/lib/Drupal/Core/Entity/EntityTypeInterface.php
Normal file
735
core/lib/Drupal/Core/Entity/EntityTypeInterface.php
Normal 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);
|
||||
|
||||
}
|
||||
41
core/lib/Drupal/Core/Entity/EntityTypeListenerInterface.php
Normal file
41
core/lib/Drupal/Core/Entity/EntityTypeListenerInterface.php
Normal 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);
|
||||
|
||||
}
|
||||
484
core/lib/Drupal/Core/Entity/EntityViewBuilder.php
Normal file
484
core/lib/Drupal/Core/Entity/EntityViewBuilder.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
162
core/lib/Drupal/Core/Entity/EntityViewBuilderInterface.php
Normal file
162
core/lib/Drupal/Core/Entity/EntityViewBuilderInterface.php
Normal 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();
|
||||
|
||||
}
|
||||
15
core/lib/Drupal/Core/Entity/EntityViewModeInterface.php
Normal file
15
core/lib/Drupal/Core/Entity/EntityViewModeInterface.php
Normal 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 {
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
||||
}
|
||||
|
|
@ -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())));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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 { }
|
||||
|
|
@ -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 {
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
||||
}
|
||||
215
core/lib/Drupal/Core/Entity/FieldableEntityInterface.php
Normal file
215
core/lib/Drupal/Core/Entity/FieldableEntityInterface.php
Normal 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();
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
}
|
||||
84
core/lib/Drupal/Core/Entity/HtmlEntityFormController.php
Normal file
84
core/lib/Drupal/Core/Entity/HtmlEntityFormController.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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';
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
||||
}
|
||||
78
core/lib/Drupal/Core/Entity/KeyValueStore/Query/Query.php
Normal file
78
core/lib/Drupal/Core/Entity/KeyValueStore/Query/Query.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
186
core/lib/Drupal/Core/Entity/Plugin/DataType/EntityAdapter.php
Normal file
186
core/lib/Drupal/Core/Entity/Plugin/DataType/EntityAdapter.php
Normal 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([]);
|
||||
}
|
||||
|
||||
}
|
||||
129
core/lib/Drupal/Core/Entity/Plugin/DataType/EntityReference.php
Normal file
129
core/lib/Drupal/Core/Entity/Plugin/DataType/EntityReference.php
Normal 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 '';
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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) { }
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
@ -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())));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
Reference in a new issue