contrib modules updates
This commit is contained in:
@@ -1,87 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\entity\Access;
|
||||
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Routing\Access\AccessInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\TempStore\PrivateTempStoreFactory;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
|
||||
/**
|
||||
* Checks if the current user has delete access to the items of the tempstore.
|
||||
*/
|
||||
class EntityDeleteMultipleAccessCheck implements AccessInterface {
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* The tempstore service.
|
||||
*
|
||||
* @var \Drupal\Core\TempStore\PrivateTempStoreFactory
|
||||
*/
|
||||
protected $tempStore;
|
||||
|
||||
/**
|
||||
* Request stack service.
|
||||
*
|
||||
* @var \Symfony\Component\HttpFoundation\RequestStack
|
||||
*/
|
||||
protected $requestStack;
|
||||
|
||||
/**
|
||||
* Constructs a new EntityDeleteMultipleAccessCheck.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
* @param \Drupal\Core\TempStore\PrivateTempStoreFactory $temp_store_factory
|
||||
* The tempstore service.
|
||||
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
|
||||
* The request stack service.
|
||||
*/
|
||||
public function __construct(EntityTypeManagerInterface $entity_type_manager, PrivateTempStoreFactory $temp_store_factory, RequestStack $request_stack) {
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->tempStore = $temp_store_factory->get('entity_delete_multiple_confirm');
|
||||
$this->requestStack = $request_stack;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the user has delete access for at least one item of the store.
|
||||
*
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* Run access checks for this account.
|
||||
* @param string $entity_type_id
|
||||
* Entity type ID.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResult
|
||||
* Allowed or forbidden, neutral if tempstore is empty.
|
||||
*/
|
||||
public function access(AccountInterface $account, $entity_type_id) {
|
||||
if (!$this->requestStack->getCurrentRequest()->getSession()) {
|
||||
return AccessResult::neutral();
|
||||
}
|
||||
$selection = $this->tempStore->get($account->id() . ':' . $entity_type_id);
|
||||
if (empty($selection) || !is_array($selection)) {
|
||||
return AccessResult::neutral();
|
||||
}
|
||||
|
||||
$entities = $this->entityTypeManager->getStorage($entity_type_id)->loadMultiple(array_keys($selection));
|
||||
foreach ($entities as $entity) {
|
||||
// As long as the user has access to delete one entity allow access to the
|
||||
// delete form. Access will be checked again in
|
||||
// Drupal\Core\Entity\Form\DeleteMultipleForm::submit() in case it has
|
||||
// changed in the meantime.
|
||||
if ($entity->access('delete', $account)) {
|
||||
return AccessResult::allowed();
|
||||
}
|
||||
}
|
||||
return AccessResult::forbidden();
|
||||
}
|
||||
|
||||
}
|
||||
+1
-1
@@ -26,7 +26,7 @@ class EntityRevisionRouteAccessChecker implements AccessInterface {
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $accessCache = array();
|
||||
protected $accessCache = [];
|
||||
|
||||
/**
|
||||
* The currently active route match object.
|
||||
|
||||
@@ -13,8 +13,8 @@ use Drupal\Core\Entity\EntityTypeInterface;
|
||||
* Provided permissions:
|
||||
* - administer $entity_type
|
||||
* - access $entity_type overview
|
||||
* - view ($bundle) $entity_type
|
||||
* - view own unpublished $entity_type
|
||||
* - view ($bundle) $entity_type
|
||||
* - update (own|any) ($bundle) $entity_type
|
||||
* - delete (own|any) ($bundle) $entity_type
|
||||
* - create $bundle $entity_type
|
||||
|
||||
@@ -2,322 +2,13 @@
|
||||
|
||||
namespace Drupal\entity\Form;
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Form\BaseFormIdInterface;
|
||||
use Drupal\Core\Form\ConfirmFormBase;
|
||||
use Drupal\Core\Messenger\MessengerInterface;
|
||||
use Drupal\Core\TypedData\TranslatableInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\TempStore\PrivateTempStoreFactory;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Drupal\Core\Entity\Form\DeleteMultipleForm as CoreDeleteMultipleForm;
|
||||
|
||||
@trigger_error('\Drupal\entity\Form\DeleteMultipleForm has been deprecated in favor of \Drupal\Core\Entity\Form\DeleteMultipleForm. Use that instead.');
|
||||
|
||||
/**
|
||||
* Provides an entities deletion confirmation form.
|
||||
*
|
||||
* @deprecated Use \Drupal\Core\Entity\Form\DeleteMultipleForm instead.
|
||||
*/
|
||||
class DeleteMultipleForm extends ConfirmFormBase implements BaseFormIdInterface {
|
||||
|
||||
/**
|
||||
* The current user.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountInterface
|
||||
*/
|
||||
protected $currentUser;
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* The tempstore.
|
||||
*
|
||||
* @var \Drupal\Core\TempStore\SharedTempStore
|
||||
*/
|
||||
protected $tempStore;
|
||||
|
||||
/**
|
||||
* The messenger service.
|
||||
*
|
||||
* @var \Drupal\Core\Messenger\MessengerInterface
|
||||
*/
|
||||
protected $messenger;
|
||||
|
||||
/**
|
||||
* The entity type ID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $entityTypeId;
|
||||
|
||||
/**
|
||||
* The selection, in the entity_id => langcodes format.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $selection = [];
|
||||
|
||||
/**
|
||||
* The entity type definition.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeInterface
|
||||
*/
|
||||
protected $entityType;
|
||||
|
||||
/**
|
||||
* Constructs a new DeleteMultiple object.
|
||||
*
|
||||
* @param \Drupal\Core\Session\AccountInterface $current_user
|
||||
* The current user.
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
* @param \Drupal\Core\TempStore\PrivateTempStoreFactory $temp_store_factory
|
||||
* The tempstore factory.
|
||||
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
|
||||
* The messenger service.
|
||||
*/
|
||||
public function __construct(AccountInterface $current_user, EntityTypeManagerInterface $entity_type_manager, PrivateTempStoreFactory $temp_store_factory, MessengerInterface $messenger) {
|
||||
$this->currentUser = $current_user;
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->tempStore = $temp_store_factory->get('entity_delete_multiple_confirm');
|
||||
$this->messenger = $messenger;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('current_user'),
|
||||
$container->get('entity_type.manager'),
|
||||
$container->get('tempstore.private'),
|
||||
$container->get('messenger')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getBaseFormId() {
|
||||
return 'entity_delete_multiple_confirm_form';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
// Get entity type ID from the route because ::buildForm has not yet been
|
||||
// called.
|
||||
$entity_type_id = $this->getRouteMatch()->getParameter('entity_type_id');
|
||||
return $entity_type_id . '_delete_multiple_confirm_form';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getQuestion() {
|
||||
return $this->formatPlural(count($this->selection), 'Are you sure you want to delete this @item?', 'Are you sure you want to delete these @items?', [
|
||||
'@item' => $this->entityType->getSingularLabel(),
|
||||
'@items' => $this->entityType->getPluralLabel(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCancelUrl() {
|
||||
if ($this->entityType->hasLinkTemplate('collection')) {
|
||||
return new Url('entity.' . $this->entityTypeId . '.collection');
|
||||
}
|
||||
else {
|
||||
return new Url('<front>');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getConfirmText() {
|
||||
return $this->t('Delete');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state, $entity_type_id = NULL) {
|
||||
$this->entityTypeId = $entity_type_id;
|
||||
$this->entityType = $this->entityTypeManager->getDefinition($this->entityTypeId);
|
||||
$this->selection = $this->tempStore->get($this->currentUser->id() . ':' . $entity_type_id);
|
||||
if (empty($this->entityTypeId) || empty($this->selection)) {
|
||||
return new RedirectResponse($this->getCancelUrl()
|
||||
->setAbsolute()
|
||||
->toString());
|
||||
}
|
||||
|
||||
$items = [];
|
||||
$entities = $this->entityTypeManager->getStorage($entity_type_id)->loadMultiple(array_keys($this->selection));
|
||||
foreach ($this->selection as $id => $selected_langcodes) {
|
||||
$entity = $entities[$id];
|
||||
foreach ($selected_langcodes as $langcode) {
|
||||
$key = $id . ':' . $langcode;
|
||||
if ($entity instanceof TranslatableInterface) {
|
||||
$entity = $entity->getTranslation($langcode);
|
||||
$default_key = $id . ':' . $entity->getUntranslated()->language()->getId();
|
||||
|
||||
// Build a nested list of translations that will be deleted if the
|
||||
// entity has multiple translations.
|
||||
$entity_languages = $entity->getTranslationLanguages();
|
||||
if (count($entity_languages) > 1 && $entity->isDefaultTranslation()) {
|
||||
$names = [];
|
||||
foreach ($entity_languages as $translation_langcode => $language) {
|
||||
$names[] = $language->getName();
|
||||
unset($items[$id . ':' . $translation_langcode]);
|
||||
}
|
||||
$items[$default_key] = [
|
||||
'label' => [
|
||||
'#markup' => $this->t('@label (Original translation) - <em>The following @entity_type translations will be deleted:</em>',
|
||||
[
|
||||
'@label' => $entity->label(),
|
||||
'@entity_type' => $this->entityType->getSingularLabel(),
|
||||
]),
|
||||
],
|
||||
'deleted_translations' => [
|
||||
'#theme' => 'item_list',
|
||||
'#items' => $names,
|
||||
],
|
||||
];
|
||||
}
|
||||
elseif (!isset($items[$default_key])) {
|
||||
$items[$key] = $entity->label();
|
||||
}
|
||||
}
|
||||
elseif (!isset($items[$key])) {
|
||||
$items[$key] = $entity->label();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$form['entities'] = [
|
||||
'#theme' => 'item_list',
|
||||
'#items' => $items,
|
||||
];
|
||||
$form = parent::buildForm($form, $form_state);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
$total_count = 0;
|
||||
$delete_entities = [];
|
||||
$delete_translations = [];
|
||||
$inaccessible_entities = [];
|
||||
$storage = $this->entityTypeManager->getStorage($this->entityTypeId);
|
||||
|
||||
$entities = $storage->loadMultiple(array_keys($this->selection));
|
||||
foreach ($this->selection as $id => $selected_langcodes) {
|
||||
$entity = $entities[$id];
|
||||
if (!$entity->access('delete', $this->currentUser)) {
|
||||
$inaccessible_entities[] = $entity;
|
||||
continue;
|
||||
}
|
||||
foreach ($selected_langcodes as $langcode) {
|
||||
if ($entity instanceof TranslatableInterface) {
|
||||
$entity = $entity->getTranslation($langcode);
|
||||
// If the entity is the default translation then deleting it will
|
||||
// delete all the translations.
|
||||
if ($entity->isDefaultTranslation()) {
|
||||
$delete_entities[$id] = $entity;
|
||||
// If there are translations already marked for deletion then remove
|
||||
// them as they will be deleted anyway.
|
||||
unset($delete_translations[$id]);
|
||||
// Update the total count. Since a single delete will delete all
|
||||
// translations, we need to add the number of translations to the
|
||||
// count.
|
||||
$total_count += count($entity->getTranslationLanguages());
|
||||
}
|
||||
// Add the translation to the list of translations to be deleted
|
||||
// unless the default translation is being deleted.
|
||||
elseif (!isset($delete_entities[$id])) {
|
||||
$delete_translations[$id][] = $entity;
|
||||
}
|
||||
}
|
||||
elseif (!isset($delete_entities[$id])) {
|
||||
$delete_entities[$id] = $entity;
|
||||
$total_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($delete_entities) {
|
||||
$storage->delete($delete_entities);
|
||||
foreach ($delete_entities as $entity) {
|
||||
$this->logger($entity->getEntityType()->getProvider())->notice('The @entity-type %label has been deleted.', [
|
||||
'@entity-type' => $entity->getEntityType()->getLowercaseLabel(),
|
||||
'%label' => $entity->label(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($delete_translations) {
|
||||
/** @var \Drupal\Core\Entity\TranslatableInterface[][] $delete_translations */
|
||||
foreach ($delete_translations as $id => $translations) {
|
||||
$entity = $entities[$id]->getUntranslated();
|
||||
foreach ($translations as $translation) {
|
||||
$entity->removeTranslation($translation->language()->getId());
|
||||
}
|
||||
$entity->save();
|
||||
foreach ($translations as $translation) {
|
||||
$this->logger($entity->getEntityType()->getProvider())->notice('The @entity-type %label @language translation has been deleted.', [
|
||||
'@entity-type' => $entity->getEntityType()->getLowercaseLabel(),
|
||||
'%label' => $entity->label(),
|
||||
'@language' => $translation->language()->getName(),
|
||||
]);
|
||||
}
|
||||
$total_count += count($translations);
|
||||
}
|
||||
}
|
||||
|
||||
if ($total_count) {
|
||||
$this->messenger->addStatus($this->getDeletedMessage($total_count));
|
||||
}
|
||||
if ($inaccessible_entities) {
|
||||
$this->messenger->addWarning($this->getInaccessibleMessage(count($inaccessible_entities)));
|
||||
}
|
||||
$this->tempStore->delete($this->currentUser->id());
|
||||
$form_state->setRedirectUrl($this->getCancelUrl());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the message to show the user after an item was deleted.
|
||||
*
|
||||
* @param int $count
|
||||
* Count of deleted translations.
|
||||
*
|
||||
* @return \Drupal\Core\StringTranslation\TranslatableMarkup
|
||||
* The item deleted message.
|
||||
*/
|
||||
protected function getDeletedMessage($count) {
|
||||
return $this->formatPlural($count, 'Deleted @count item.', 'Deleted @count items.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the message to show the user when an item has not been deleted.
|
||||
*
|
||||
* @param int $count
|
||||
* Count of deleted translations.
|
||||
*
|
||||
* @return \Drupal\Core\StringTranslation\TranslatableMarkup
|
||||
* The item inaccessible message.
|
||||
*/
|
||||
protected function getInaccessibleMessage($count) {
|
||||
return $this->formatPlural($count, "@count item has not been deleted because you do not have the necessary permissions.", "@count items have not been deleted because you do not have the necessary permissions.");
|
||||
}
|
||||
|
||||
}
|
||||
class DeleteMultipleForm extends CoreDeleteMultipleForm {}
|
||||
|
||||
@@ -119,10 +119,17 @@ class RevisionRevertForm extends ConfirmFormBase {
|
||||
$original_revision_timestamp = $this->revision->getRevisionCreationTime();
|
||||
|
||||
$this->revision->setRevisionLogMessage($this->t('Copy of the revision from %date.', ['%date' => $this->dateFormatter->format($original_revision_timestamp)]));
|
||||
drupal_set_message(t('@type %title has been reverted to the revision from %revision-date.', ['@type' => $this->getBundleLabel($this->revision), '%title' => $this->revision->label(), '%revision-date' => $this->dateFormatter->format($original_revision_timestamp)]));
|
||||
$this->messenger()->addStatus(t('@type %title has been reverted to the revision from %revision-date.', [
|
||||
'@type' => $this->getBundleLabel($this->revision),
|
||||
'%title' => $this->revision->label(),
|
||||
'%revision-date' => $this->dateFormatter->format($original_revision_timestamp),
|
||||
]));
|
||||
}
|
||||
else {
|
||||
drupal_set_message(t('@type %title has been reverted', ['@type' => $this->getBundleLabel($this->revision), '%title' => $this->revision->label()]));
|
||||
$this->messenger()->addStatus(t('@type %title has been reverted', [
|
||||
'@type' => $this->getBundleLabel($this->revision),
|
||||
'%title' => $this->revision->label(),
|
||||
]));
|
||||
}
|
||||
|
||||
$this->revision->save();
|
||||
|
||||
@@ -11,15 +11,14 @@ use Drupal\Core\Form\FormStateInterface;
|
||||
/**
|
||||
* Extends the base entity form with revision support in the UI.
|
||||
*
|
||||
* @deprecated in favor of \Drupal\Core\Entity\ContentEntityForm. Use that
|
||||
* instead.
|
||||
* @deprecated Use \Drupal\Core\Entity\ContentEntityForm instead.
|
||||
*/
|
||||
class RevisionableContentEntityForm extends ContentEntityForm {
|
||||
|
||||
/**
|
||||
* The entity being used by this form.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityInterface|\Drupal\Core\Entity\RevisionableInterface|\Drupal\entity\Revision\EntityRevisionLogInterface
|
||||
* @var \Drupal\Core\Entity\ContentEntityInterface|\Drupal\Core\Entity\RevisionLogInterface
|
||||
*/
|
||||
protected $entity;
|
||||
|
||||
@@ -49,7 +48,8 @@ class RevisionableContentEntityForm extends ContentEntityForm {
|
||||
* The bundle entity, or NULL if there is none.
|
||||
*/
|
||||
protected function getBundleEntity() {
|
||||
if ($bundle_key = $this->entity->getEntityType()->getKey('bundle')) {
|
||||
if ($this->entity->getEntityType()->getBundleEntityType()) {
|
||||
$bundle_key = $this->entity->getEntityType()->getKey('bundle');
|
||||
return $this->entity->{$bundle_key}->referencedEntities()[0];
|
||||
}
|
||||
return NULL;
|
||||
|
||||
@@ -30,6 +30,12 @@ class EntityCollectionLocalActionProvider implements EntityLocalActionProviderIn
|
||||
/* @see \Drupal\entity\Menu\EntityAddLocalAction::getTitle() */
|
||||
'title' => 'Add ' . $entity_type->getSingularLabel(),
|
||||
'route_name' => $route_name,
|
||||
'options' => [
|
||||
// Redirect back to the collection after form submission.
|
||||
'query' => [
|
||||
'destination' => $entity_type->getLinkTemplate('collection'),
|
||||
],
|
||||
],
|
||||
'appears_on' => ["entity.$entity_type_id.collection"],
|
||||
'class' => EntityAddLocalAction::class,
|
||||
];
|
||||
|
||||
@@ -2,96 +2,19 @@
|
||||
|
||||
namespace Drupal\entity\Plugin\Action;
|
||||
|
||||
use Drupal\Core\Action\ActionBase;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\TempStore\PrivateTempStoreFactory;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Drupal\Core\Action\Plugin\Action\DeleteAction as CoreDeleteAction;
|
||||
|
||||
@trigger_error('\Drupal\entity\Plugin\Action\DeleteAction has been deprecated in favor of \Drupal\Core\Action\Plugin\Action\DeleteAction. Use that instead.');
|
||||
|
||||
/**
|
||||
* Redirects to an entity deletion form.
|
||||
*
|
||||
* @deprecated Use "entity:delete_action" instead.
|
||||
*
|
||||
* @Action(
|
||||
* id = "entity_delete_action",
|
||||
* label = @Translation("Delete entity"),
|
||||
* deriver = "Drupal\entity\Plugin\Action\Derivative\DeleteActionDeriver",
|
||||
* )
|
||||
*/
|
||||
class DeleteAction extends ActionBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The tempstore object.
|
||||
*
|
||||
* @var \Drupal\Core\TempStore\SharedTempStore
|
||||
*/
|
||||
protected $tempStore;
|
||||
|
||||
/**
|
||||
* The current user.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountInterface
|
||||
*/
|
||||
protected $currentUser;
|
||||
|
||||
/**
|
||||
* Constructs a new DeleteAction 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\TempStore\PrivateTempStoreFactory $temp_store_factory
|
||||
* The tempstore factory.
|
||||
* @param \Drupal\Core\Session\AccountInterface $current_user
|
||||
* Current user.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, PrivateTempStoreFactory $temp_store_factory, AccountInterface $current_user) {
|
||||
$this->currentUser = $current_user;
|
||||
$this->tempStore = $temp_store_factory->get('entity_delete_multiple_confirm');
|
||||
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('tempstore.private'),
|
||||
$container->get('current_user')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function executeMultiple(array $entities) {
|
||||
/** @var \Drupal\Core\Entity\EntityInterface[] $entities */
|
||||
$selection = [];
|
||||
foreach ($entities as $entity) {
|
||||
$langcode = $entity->language()->getId();
|
||||
$selection[$entity->id()][$langcode] = $langcode;
|
||||
}
|
||||
$this->tempStore->set($this->currentUser->id() . ':' . $this->getPluginDefinition()['type'], $selection);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function execute($object = NULL) {
|
||||
$this->executeMultiple([$object]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
|
||||
return $object->access('delete', $account, $return_as_object);
|
||||
}
|
||||
|
||||
}
|
||||
class DeleteAction extends CoreDeleteAction {}
|
||||
|
||||
+7
-3
@@ -4,13 +4,14 @@ namespace Drupal\entity\Plugin\Action\Derivative;
|
||||
|
||||
use Drupal\Component\Plugin\Derivative\DeriverBase;
|
||||
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides a delete action for each content entity type.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
class DeleteActionDeriver extends DeriverBase implements ContainerDeriverInterface {
|
||||
|
||||
@@ -46,7 +47,7 @@ class DeleteActionDeriver extends DeriverBase implements ContainerDeriverInterfa
|
||||
$definitions = [];
|
||||
foreach ($this->getParticipatingEntityTypes() as $entity_type_id => $entity_type) {
|
||||
$definition = $base_plugin_definition;
|
||||
$definition['label'] = t('Delete @entity_type', ['@entity_type' => $entity_type->getSingularLabel()]);
|
||||
$definition['label'] = t('Delete @entity_type (Deprecated)', ['@entity_type' => $entity_type->getSingularLabel()]);
|
||||
$definition['type'] = $entity_type_id;
|
||||
$definition['confirm_form_route_name'] = 'entity.' . $entity_type_id . '.delete_multiple_form';
|
||||
$definitions[$entity_type_id] = $definition;
|
||||
@@ -69,7 +70,10 @@ class DeleteActionDeriver extends DeriverBase implements ContainerDeriverInterfa
|
||||
protected function getParticipatingEntityTypes() {
|
||||
$entity_types = $this->entityTypeManager->getDefinitions();
|
||||
$entity_types = array_filter($entity_types, function (EntityTypeInterface $entity_type) {
|
||||
return $entity_type->entityClassImplements(ContentEntityInterface::class) && $entity_type->hasLinkTemplate('delete-multiple-form');
|
||||
// Core requires a "delete-multiple-confirm" form to be declared as well,
|
||||
// if it's missing, it's safe to assume that the entity type is still
|
||||
// relying on previous Entity API contrib behavior.
|
||||
return $entity_type->hasLinkTemplate('delete-multiple-form') && !$entity_type->hasHandlerClass('form', 'delete-multiple-confirm');
|
||||
});
|
||||
|
||||
return $entity_types;
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\entity\QueryAccess;
|
||||
|
||||
/**
|
||||
* Represents a single query access condition.
|
||||
*/
|
||||
final class Condition {
|
||||
|
||||
/**
|
||||
* The supported operators.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected static $supportedOperators = [
|
||||
'=', '<>', '<', '<=', '>', '>=', 'BETWEEN', 'NOT BETWEEN',
|
||||
'IN', 'NOT IN', 'IS NULL', 'IS NOT NULL',
|
||||
];
|
||||
|
||||
/**
|
||||
* The field.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $field;
|
||||
|
||||
/**
|
||||
* The value.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
protected $value;
|
||||
|
||||
/**
|
||||
* The operator.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $operator;
|
||||
|
||||
/**
|
||||
* Constructs a new Condition object.
|
||||
*
|
||||
* @param string $field
|
||||
* The field, with an optional column name. E.g: 'uid', 'address.locality'.
|
||||
* @param mixed $value
|
||||
* The value.
|
||||
* @param string $operator
|
||||
* The operator.
|
||||
* Possible values: =, <>, <, <=, >, >=, BETWEEN, NOT BETWEEN,
|
||||
* IN, NOT IN, IS NULL, IS NOT NULL.
|
||||
*/
|
||||
public function __construct($field, $value, $operator = NULL) {
|
||||
// Provide a default based on the data type of the value.
|
||||
if (!isset($operator)) {
|
||||
$operator = is_array($value) ? 'IN' : '=';
|
||||
}
|
||||
// Validate the selected operator.
|
||||
if (!in_array($operator, self::$supportedOperators)) {
|
||||
throw new \InvalidArgumentException(sprintf('Unrecognized operator "%s".', $operator));
|
||||
}
|
||||
|
||||
$this->field = $field;
|
||||
$this->value = $value;
|
||||
$this->operator = $operator;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getField() {
|
||||
return $this->field;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getValue() {
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getOperator() {
|
||||
return $this->operator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the string representation of the condition.
|
||||
*
|
||||
* Used for debugging purposes.
|
||||
*
|
||||
* @return string
|
||||
* The string representation of the condition.
|
||||
*/
|
||||
public function __toString() {
|
||||
if (in_array($this->operator, ['IS NULL', 'IS NOT NULL'])) {
|
||||
return "{$this->field} {$this->operator}";
|
||||
}
|
||||
else {
|
||||
if (is_array($this->value)) {
|
||||
$value = "['" . implode("', '", $this->value) . "']";
|
||||
}
|
||||
else {
|
||||
$value = "'" . $this->value . "'";
|
||||
}
|
||||
|
||||
return "{$this->field} {$this->operator} $value";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\entity\QueryAccess;
|
||||
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
|
||||
use Drupal\Core\Cache\RefinableCacheableDependencyTrait;
|
||||
|
||||
/**
|
||||
* Represents a group of query access conditions.
|
||||
*
|
||||
* Used by query access handlers for filtering lists of entities based on
|
||||
* granted permissions.
|
||||
*
|
||||
* Examples:
|
||||
* @code
|
||||
* // Filter by node type and uid.
|
||||
* $condition_group = new ConditionGroup();
|
||||
* $condition_group->addCondition('type', ['article', 'page']);
|
||||
* $condition_group->addCondition('uid', '1');
|
||||
*
|
||||
* // Filter by node type or status.
|
||||
* $condition_group = new ConditionGroup('OR');
|
||||
* $condition_group->addCondition('type', ['article', 'page']);
|
||||
* $condition_group->addCondition('status', '1', '<>');
|
||||
*
|
||||
* // Nested condition groups: node type AND (uid OR status).
|
||||
* $condition_group = new ConditionGroup();
|
||||
* $condition_group->addCondition('type', ['article', 'page']);
|
||||
* $condition_group->addCondition((new ConditionGroup('OR'))
|
||||
* ->addCondition('uid', 1)
|
||||
* ->addCondition('status', '1')
|
||||
* );
|
||||
* @endcode
|
||||
*/
|
||||
final class ConditionGroup implements \Countable, RefinableCacheableDependencyInterface {
|
||||
|
||||
use RefinableCacheableDependencyTrait;
|
||||
|
||||
/**
|
||||
* The conjunction.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $conjunction;
|
||||
|
||||
/**
|
||||
* The conditions.
|
||||
*
|
||||
* @var \Drupal\entity\QueryAccess\Condition[]|\Drupal\entity\QueryAccess\ConditionGroup[]
|
||||
*/
|
||||
protected $conditions = [];
|
||||
|
||||
/**
|
||||
* Whether the condition group is always FALSE.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $alwaysFalse = FALSE;
|
||||
|
||||
/**
|
||||
* Constructs a new ConditionGroup object.
|
||||
*
|
||||
* @param string $conjunction
|
||||
* The conjunction.
|
||||
*/
|
||||
public function __construct($conjunction = 'AND') {
|
||||
$this->conjunction = $conjunction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the conjunction.
|
||||
*
|
||||
* @return string
|
||||
* The conjunction. Possible values: AND, OR.
|
||||
*/
|
||||
public function getConjunction() {
|
||||
return $this->conjunction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all conditions and nested condition groups.
|
||||
*
|
||||
* @return \Drupal\entity\QueryAccess\Condition[]|\Drupal\entity\QueryAccess\ConditionGroup[]
|
||||
* The conditions, where each one is either a Condition or a nested
|
||||
* ConditionGroup. Returned by reference, to allow callers to replace
|
||||
* or remove conditions.
|
||||
*/
|
||||
public function &getConditions() {
|
||||
return $this->conditions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a condition.
|
||||
*
|
||||
* @param string|\Drupal\entity\QueryAccess\ConditionGroup $field
|
||||
* Either a condition group (for nested AND/OR conditions), or a
|
||||
* field name with an optional column name. E.g: 'uid', 'address.locality'.
|
||||
* @param mixed $value
|
||||
* The value.
|
||||
* @param string $operator
|
||||
* The operator.
|
||||
* Possible values: =, <>, <, <=, >, >=, BETWEEN, NOT BETWEEN,
|
||||
* IN, NOT IN, IS NULL, IS NOT NULL.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addCondition($field, $value = NULL, $operator = NULL) {
|
||||
if ($field instanceof ConditionGroup) {
|
||||
if ($field->count() === 1) {
|
||||
// The condition group only has a single condition, merge it.
|
||||
$this->conditions[] = reset($field->getConditions());
|
||||
$this->addCacheTags($field->getCacheTags());
|
||||
$this->addCacheContexts($field->getCacheContexts());
|
||||
$this->mergeCacheMaxAge($field->getCacheMaxAge());
|
||||
}
|
||||
elseif ($field->count() > 1) {
|
||||
$this->conditions[] = $field;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$this->conditions[] = new Condition($field, $value, $operator);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether the condition group is always FALSE.
|
||||
*
|
||||
* Used when the user doesn't have access to any entities, to ensure that a
|
||||
* query returns no results.
|
||||
*
|
||||
* @return bool
|
||||
* Whether the condition group is always FALSE.
|
||||
*/
|
||||
public function isAlwaysFalse() {
|
||||
return $this->alwaysFalse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the condition group should always be FALSE.
|
||||
*
|
||||
* @param bool $always_false
|
||||
* Whether the condition group should always be FALSE.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function alwaysFalse($always_false = TRUE) {
|
||||
$this->alwaysFalse = $always_false;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones the contained conditions when the condition group is cloned.
|
||||
*/
|
||||
public function __clone() {
|
||||
foreach ($this->conditions as $i => $condition) {
|
||||
$this->conditions[$i] = clone $condition;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the string representation of the condition group.
|
||||
*
|
||||
* @return string
|
||||
* The string representation of the condition group.
|
||||
*/
|
||||
public function __toString() {
|
||||
// Special case for a single, nested condition group:
|
||||
if (count($this->conditions) == 1) {
|
||||
return (string) reset($this->conditions);
|
||||
}
|
||||
$lines = [];
|
||||
foreach ($this->conditions as $condition) {
|
||||
$lines[] = str_replace("\n", "\n ", (string) $condition);
|
||||
}
|
||||
return $lines ? "(\n " . implode("\n {$this->conjunction}\n ", $lines) . "\n)" : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function count() {
|
||||
return count($this->conditions);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheTags() {
|
||||
$tags = $this->cacheTags;
|
||||
foreach ($this->conditions as $condition) {
|
||||
if ($condition instanceof ConditionGroup) {
|
||||
$tags = array_merge($tags, $condition->getCacheTags());
|
||||
}
|
||||
}
|
||||
return Cache::mergeTags($tags, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheContexts() {
|
||||
$cache_contexts = $this->cacheContexts;
|
||||
foreach ($this->conditions as $condition) {
|
||||
if ($condition instanceof ConditionGroup) {
|
||||
$cache_contexts = array_merge($cache_contexts, $condition->getCacheContexts());
|
||||
}
|
||||
}
|
||||
return Cache::mergeContexts($cache_contexts);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheMaxAge() {
|
||||
$max_age = $this->cacheMaxAge;
|
||||
foreach ($this->conditions as $condition) {
|
||||
if ($condition instanceof ConditionGroup) {
|
||||
$max_age = Cache::mergeMaxAges($max_age, $condition->getCacheMaxAge());
|
||||
}
|
||||
}
|
||||
return $max_age;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\entity\QueryAccess;
|
||||
|
||||
use Drupal\Core\Cache\CacheableMetadata;
|
||||
use Drupal\Core\Database\Query\SelectInterface;
|
||||
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
|
||||
use Drupal\Core\Entity\EntityFieldManagerInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Entity\Query\Sql\Tables;
|
||||
use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
|
||||
use Drupal\Core\Render\RendererInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
|
||||
/**
|
||||
* Defines a class for altering entity queries.
|
||||
*
|
||||
* EntityQuery doesn't have an alter hook, forcing this class to operate
|
||||
* on the underlying SQL query, duplicating the EntityQuery condition logic.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class EntityQueryAlter implements ContainerInjectionInterface {
|
||||
|
||||
/**
|
||||
* The entity field manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityFieldManagerInterface
|
||||
*/
|
||||
protected $entityFieldManager;
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* The renderer.
|
||||
*
|
||||
* @var \Drupal\Core\Render\RendererInterface
|
||||
*/
|
||||
protected $renderer;
|
||||
|
||||
/**
|
||||
* The request stack.
|
||||
*
|
||||
* @var \Symfony\Component\HttpFoundation\RequestStack
|
||||
*/
|
||||
protected $requestStack;
|
||||
|
||||
/**
|
||||
* Constructs a new EntityQueryAlter object.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
|
||||
* The entity field manager.
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
* @param \Drupal\Core\Render\RendererInterface $renderer
|
||||
* The renderer.
|
||||
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
|
||||
* The request stack.
|
||||
*/
|
||||
public function __construct(EntityFieldManagerInterface $entity_field_manager, EntityTypeManagerInterface $entity_type_manager, RendererInterface $renderer, RequestStack $request_stack) {
|
||||
$this->entityFieldManager = $entity_field_manager;
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->renderer = $renderer;
|
||||
$this->requestStack = $request_stack;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('entity_field.manager'),
|
||||
$container->get('entity_type.manager'),
|
||||
$container->get('renderer'),
|
||||
$container->get('request_stack')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Alters the select query for the given entity type.
|
||||
*
|
||||
* @param \Drupal\Core\Database\Query\SelectInterface $query
|
||||
* The select query.
|
||||
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
|
||||
* The entity type.
|
||||
*/
|
||||
public function alter(SelectInterface $query, EntityTypeInterface $entity_type) {
|
||||
if (!$entity_type->hasHandlerClass('query_access')) {
|
||||
return;
|
||||
}
|
||||
$entity_type_id = $entity_type->id();
|
||||
$storage = $this->entityTypeManager->getStorage($entity_type_id);
|
||||
if (!$storage instanceof SqlContentEntityStorage) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var \Drupal\entity\QueryAccess\QueryAccessHandlerInterface $query_access */
|
||||
$query_access = $this->entityTypeManager->getHandler($entity_type_id, 'query_access');
|
||||
$conditions = $query_access->getConditions('view');
|
||||
if ($conditions->isAlwaysFalse()) {
|
||||
$query->where('1 = 0');
|
||||
}
|
||||
elseif (count($conditions)) {
|
||||
$sql_conditions = $this->mapConditions($conditions, $query);
|
||||
$query->condition($sql_conditions);
|
||||
}
|
||||
|
||||
$this->applyCacheability(CacheableMetadata::createFromObject($conditions));
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps an entity type's access conditions to SQL conditions.
|
||||
*
|
||||
* @param \Drupal\entity\QueryAccess\ConditionGroup $conditions
|
||||
* The access conditions.
|
||||
* @param \Drupal\Core\Database\Query\SelectInterface $query
|
||||
* The SQL query.
|
||||
* @param bool $nested_inside_or
|
||||
* Whether the access conditions are nested inside an OR condition.
|
||||
*
|
||||
* @return \Drupal\Core\Database\Query\ConditionInterface
|
||||
* The SQL conditions.
|
||||
*/
|
||||
protected function mapConditions(ConditionGroup $conditions, SelectInterface $query, $nested_inside_or = FALSE) {
|
||||
$sql_condition = $query->conditionGroupFactory($conditions->getConjunction());
|
||||
$tables = new Tables($query);
|
||||
$nested_inside_or = $nested_inside_or || $conditions->getConjunction() == 'OR';
|
||||
foreach ($conditions->getConditions() as $condition) {
|
||||
if ($condition instanceof ConditionGroup) {
|
||||
$nested_sql_conditions = $this->mapConditions($condition, $query, $nested_inside_or);
|
||||
$sql_condition->condition($nested_sql_conditions);
|
||||
}
|
||||
else {
|
||||
// Access conditions don't specify a langcode.
|
||||
$langcode = NULL;
|
||||
$type = $nested_inside_or || $condition->getOperator() === 'IS NULL' ? 'LEFT' : 'INNER';
|
||||
$sql_field = $tables->addField($condition->getField(), $type, $langcode);
|
||||
$value = $condition->getValue();
|
||||
$operator = $condition->getOperator();
|
||||
// Using LIKE/NOT LIKE ensures a case insensitive comparison.
|
||||
// @see \Drupal\Core\Entity\Query\Sql\Condition::translateCondition().
|
||||
$case_sensitive = $tables->isFieldCaseSensitive($condition->getField());
|
||||
$operator_map = [
|
||||
'=' => 'LIKE',
|
||||
'<>' => 'NOT LIKE',
|
||||
];
|
||||
if (!$case_sensitive && isset($operator_map[$operator])) {
|
||||
$operator = $operator_map[$operator];
|
||||
$value = $query->escapeLike($value);
|
||||
}
|
||||
|
||||
$sql_condition->condition($sql_field, $value, $operator);
|
||||
}
|
||||
}
|
||||
|
||||
return $sql_condition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the cacheablity metadata to the current request.
|
||||
*
|
||||
* @param \Drupal\Core\Cache\CacheableMetadata $cacheable_metadata
|
||||
* The cacheability metadata.
|
||||
*/
|
||||
protected function applyCacheability(CacheableMetadata $cacheable_metadata) {
|
||||
$request = $this->requestStack->getCurrentRequest();
|
||||
if ($request->isMethodCacheable() && $this->renderer->hasRenderContext()) {
|
||||
$build = [];
|
||||
$cacheable_metadata->applyTo($build);
|
||||
$this->renderer->render($build);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\entity\QueryAccess;
|
||||
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Symfony\Component\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* Defines the query access event.
|
||||
*
|
||||
* Allows modules to modify access conditions before they're applied to a query.
|
||||
*
|
||||
* The event ID is dynamic: entity.query_access.$entity_type_id
|
||||
*/
|
||||
class QueryAccessEvent extends Event {
|
||||
|
||||
/**
|
||||
* The conditions.
|
||||
*
|
||||
* @var \Drupal\entity\QueryAccess\ConditionGroup
|
||||
*/
|
||||
protected $conditions;
|
||||
|
||||
/**
|
||||
* The operation.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $operation;
|
||||
|
||||
/**
|
||||
* The user for which to restrict access.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountInterface
|
||||
*/
|
||||
protected $account;
|
||||
|
||||
/**
|
||||
* Constructs a new QueryAccessEvent.
|
||||
*
|
||||
* @param \Drupal\entity\QueryAccess\ConditionGroup $conditions
|
||||
* The conditions.
|
||||
* @param string $operation
|
||||
* The operation. Usually one of "view", "update" or "delete".
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* The user for which to restrict access.
|
||||
*/
|
||||
public function __construct(ConditionGroup $conditions, $operation, AccountInterface $account) {
|
||||
$this->conditions = $conditions;
|
||||
$this->operation = $operation;
|
||||
$this->account = $account;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the conditions.
|
||||
*
|
||||
* If $conditions->isAlwaysFalse() is TRUE, the user doesn't have access to
|
||||
* any entities, and the query is expected to return no results.
|
||||
* This can be reversed by calling $conditions->alwaysFalse(FALSE).
|
||||
*
|
||||
* If $conditions->isAlwaysFalse() is FALSE, and the condition group is
|
||||
* empty (count is 0), the user has full access, and the query doesn't
|
||||
* need to be restricted.
|
||||
*
|
||||
* @return \Drupal\entity\QueryAccess\ConditionGroup
|
||||
* The conditions.
|
||||
*/
|
||||
public function getConditions() {
|
||||
return $this->conditions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the operation.
|
||||
*
|
||||
* @return string
|
||||
* The operation. Usually one of "view", "update" or "delete".
|
||||
*/
|
||||
public function getOperation() {
|
||||
return $this->operation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the user for which to restrict access.
|
||||
*
|
||||
* @return \Drupal\Core\Session\AccountInterface
|
||||
* The user.
|
||||
*/
|
||||
public function getAccount() {
|
||||
return $this->account;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\entity\QueryAccess;
|
||||
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
|
||||
/**
|
||||
* Controls query access based on the generic entity permissions.
|
||||
*
|
||||
* @see \Drupal\entity\EntityAccessControlHandler
|
||||
* @see \Drupal\entity\EntityPermissionProvider
|
||||
*/
|
||||
class QueryAccessHandler extends QueryAccessHandlerBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function buildEntityOwnerConditions($operation, AccountInterface $account) {
|
||||
if ($operation == 'view') {
|
||||
// EntityPermissionProvider doesn't provide own/any view permissions.
|
||||
return $this->buildEntityConditions($operation, $account);
|
||||
}
|
||||
|
||||
return parent::buildEntityOwnerConditions($operation, $account);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\entity\QueryAccess;
|
||||
|
||||
use Drupal\Core\Entity\EntityHandlerInterface;
|
||||
use Drupal\Core\Entity\EntityPublishedInterface;
|
||||
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\user\EntityOwnerInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
|
||||
/**
|
||||
* Provides common logic for query access handlers.
|
||||
*
|
||||
* @see \Drupal\entity\QueryAccess\QueryAccessHandler
|
||||
* @see \Drupal\entity\QueryAccess\UncacheableQueryAccessHandler
|
||||
*/
|
||||
abstract class QueryAccessHandlerBase implements EntityHandlerInterface, QueryAccessHandlerInterface {
|
||||
|
||||
/**
|
||||
* The entity type.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeInterface
|
||||
*/
|
||||
protected $entityType;
|
||||
|
||||
/**
|
||||
* The entity type bundle info.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
|
||||
*/
|
||||
protected $bundleInfo;
|
||||
|
||||
/**
|
||||
* The event dispatcher.
|
||||
*
|
||||
* @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
|
||||
*/
|
||||
protected $eventDispatcher;
|
||||
|
||||
/**
|
||||
* The current user.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountInterface
|
||||
*/
|
||||
protected $currentUser;
|
||||
|
||||
/**
|
||||
* Constructs a new QueryAccessHandlerBase object.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
|
||||
* The entity type.
|
||||
* @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $bundle_info
|
||||
* The entity type bundle info.
|
||||
* @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
|
||||
* The event dispatcher.
|
||||
* @param \Drupal\Core\Session\AccountInterface $current_user
|
||||
* The current user.
|
||||
*/
|
||||
public function __construct(EntityTypeInterface $entity_type, EntityTypeBundleInfoInterface $bundle_info, EventDispatcherInterface $event_dispatcher, AccountInterface $current_user) {
|
||||
$this->entityType = $entity_type;
|
||||
$this->bundleInfo = $bundle_info;
|
||||
$this->eventDispatcher = $event_dispatcher;
|
||||
$this->currentUser = $current_user;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
|
||||
return new static(
|
||||
$entity_type,
|
||||
$container->get('entity_type.bundle.info'),
|
||||
$container->get('event_dispatcher'),
|
||||
$container->get('current_user')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getConditions($operation, AccountInterface $account = NULL) {
|
||||
$account = $account ?: $this->currentUser;
|
||||
$entity_type_id = $this->entityType->id();
|
||||
$conditions = $this->buildConditions($operation, $account);
|
||||
|
||||
// Allow other modules to modify the conditions before they are used.
|
||||
$event = new QueryAccessEvent($conditions, $operation, $account);
|
||||
$this->eventDispatcher->dispatch("entity.query_access.{$entity_type_id}", $event);
|
||||
|
||||
return $conditions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the conditions for the given operation and user.
|
||||
*
|
||||
* @param string $operation
|
||||
* The access operation. Usually one of "view", "update" or "delete".
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* The user for which to restrict access.
|
||||
*
|
||||
* @return \Drupal\entity\QueryAccess\ConditionGroup
|
||||
* The conditions.
|
||||
*/
|
||||
public function buildConditions($operation, AccountInterface $account) {
|
||||
$entity_type_id = $this->entityType->id();
|
||||
$has_owner = $this->entityType->entityClassImplements(EntityOwnerInterface::class);
|
||||
|
||||
if ($account->hasPermission("administer {$entity_type_id}")) {
|
||||
// The user has full access to all operations, no conditions needed.
|
||||
$conditions = new ConditionGroup('OR');
|
||||
$conditions->addCacheContexts(['user.permissions']);
|
||||
return $conditions;
|
||||
}
|
||||
|
||||
if ($has_owner) {
|
||||
$entity_conditions = $this->buildEntityOwnerConditions($operation, $account);
|
||||
}
|
||||
else {
|
||||
$entity_conditions = $this->buildEntityConditions($operation, $account);
|
||||
}
|
||||
|
||||
$conditions = NULL;
|
||||
if ($operation == 'view' && $this->entityType->entityClassImplements(EntityPublishedInterface::class)) {
|
||||
$uid_key = $this->entityType->getKey('uid');
|
||||
$published_key = $this->entityType->getKey('published');
|
||||
$published_conditions = NULL;
|
||||
$unpublished_conditions = NULL;
|
||||
|
||||
if ($entity_conditions) {
|
||||
// Restrict the existing conditions to published entities only.
|
||||
$published_conditions = new ConditionGroup('AND');
|
||||
$published_conditions->addCacheContexts(['user.permissions']);
|
||||
$published_conditions->addCondition($entity_conditions);
|
||||
$published_conditions->addCondition($published_key, '1');
|
||||
}
|
||||
if ($has_owner && $account->hasPermission("view own unpublished $entity_type_id")) {
|
||||
$unpublished_conditions = new ConditionGroup('AND');
|
||||
$unpublished_conditions->addCacheContexts(['user']);
|
||||
$unpublished_conditions->addCondition($uid_key, $account->id());
|
||||
$unpublished_conditions->addCondition($published_key, '0');
|
||||
}
|
||||
|
||||
if ($published_conditions && $unpublished_conditions) {
|
||||
$conditions = new ConditionGroup('OR');
|
||||
$conditions->addCondition($published_conditions);
|
||||
$conditions->addCondition($unpublished_conditions);
|
||||
}
|
||||
elseif ($published_conditions) {
|
||||
$conditions = $published_conditions;
|
||||
}
|
||||
elseif ($unpublished_conditions) {
|
||||
$conditions = $unpublished_conditions;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$conditions = $entity_conditions;
|
||||
}
|
||||
|
||||
if (!$conditions) {
|
||||
// The user doesn't have access to any entities.
|
||||
// Falsify the query to ensure no results are returned.
|
||||
$conditions = new ConditionGroup('OR');
|
||||
$conditions->addCacheContexts(['user.permissions']);
|
||||
$conditions->alwaysFalse();
|
||||
}
|
||||
|
||||
return $conditions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the conditions for entities that have an owner.
|
||||
*
|
||||
* @param string $operation
|
||||
* The access operation. Usually one of "view", "update" or "delete".
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* The user for which to restrict access.
|
||||
*
|
||||
* @return \Drupal\entity\QueryAccess\ConditionGroup|null
|
||||
* The conditions, or NULL if the user doesn't have access to any entity.
|
||||
*/
|
||||
protected function buildEntityOwnerConditions($operation, AccountInterface $account) {
|
||||
$entity_type_id = $this->entityType->id();
|
||||
$uid_key = $this->entityType->getKey('uid');
|
||||
$bundle_key = $this->entityType->getKey('bundle');
|
||||
|
||||
$conditions = new ConditionGroup('OR');
|
||||
$conditions->addCacheContexts(['user.permissions']);
|
||||
// Any $entity_type permission.
|
||||
if ($account->hasPermission("$operation any $entity_type_id")) {
|
||||
// The user has full access, no conditions needed.
|
||||
return $conditions;
|
||||
}
|
||||
|
||||
// Own $entity_type permission.
|
||||
if ($account->hasPermission("$operation own $entity_type_id")) {
|
||||
$conditions->addCacheContexts(['user']);
|
||||
$conditions->addCondition($uid_key, $account->id());
|
||||
}
|
||||
|
||||
$bundles = array_keys($this->bundleInfo->getBundleInfo($entity_type_id));
|
||||
$bundles_with_any_permission = [];
|
||||
$bundles_with_own_permission = [];
|
||||
foreach ($bundles as $bundle) {
|
||||
if ($account->hasPermission("$operation any $bundle $entity_type_id")) {
|
||||
$bundles_with_any_permission[] = $bundle;
|
||||
}
|
||||
if ($account->hasPermission("$operation own $bundle $entity_type_id")) {
|
||||
$bundles_with_own_permission[] = $bundle;
|
||||
}
|
||||
}
|
||||
// Any $bundle permission.
|
||||
if ($bundles_with_any_permission) {
|
||||
$conditions->addCondition($bundle_key, $bundles_with_any_permission);
|
||||
}
|
||||
// Own $bundle permission.
|
||||
if ($bundles_with_own_permission) {
|
||||
$conditions->addCacheContexts(['user']);
|
||||
$conditions->addCondition((new ConditionGroup('AND'))
|
||||
->addCondition($uid_key, $account->id())
|
||||
->addCondition($bundle_key, $bundles_with_own_permission)
|
||||
);
|
||||
}
|
||||
|
||||
return $conditions->count() ? $conditions : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the conditions for entities that do not have an owner.
|
||||
*
|
||||
* @param string $operation
|
||||
* The access operation. Usually one of "view", "update" or "delete".
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* The user for which to restrict access.
|
||||
*
|
||||
* @return \Drupal\entity\QueryAccess\ConditionGroup|null
|
||||
* The conditions, or NULL if the user doesn't have access to any entity.
|
||||
*/
|
||||
protected function buildEntityConditions($operation, AccountInterface $account) {
|
||||
$entity_type_id = $this->entityType->id();
|
||||
$bundle_key = $this->entityType->getKey('bundle');
|
||||
|
||||
$conditions = new ConditionGroup('OR');
|
||||
$conditions->addCacheContexts(['user.permissions']);
|
||||
// The $entity_type permission.
|
||||
if ($account->hasPermission("$operation $entity_type_id")) {
|
||||
// The user has full access, no conditions needed.
|
||||
return $conditions;
|
||||
}
|
||||
|
||||
$bundles = array_keys($this->bundleInfo->getBundleInfo($entity_type_id));
|
||||
$bundles_with_any_permission = [];
|
||||
foreach ($bundles as $bundle) {
|
||||
if ($account->hasPermission("$operation $bundle $entity_type_id")) {
|
||||
$bundles_with_any_permission[] = $bundle;
|
||||
}
|
||||
}
|
||||
// The $bundle permission.
|
||||
if ($bundles_with_any_permission) {
|
||||
$conditions->addCondition($bundle_key, $bundles_with_any_permission);
|
||||
}
|
||||
|
||||
return $conditions->count() ? $conditions : NULL;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\entity\QueryAccess;
|
||||
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
|
||||
/**
|
||||
* Query access handlers control access to entities in queries.
|
||||
*
|
||||
* An entity defines a query access handler in its annotation:
|
||||
* @code
|
||||
* query_access = "\Drupal\entity\QueryAccess\QueryAccessHandler"
|
||||
* @code
|
||||
* The handler builds a set of conditions which are then applied to a query
|
||||
* to filter it. For example, if the user #22 only has access to view
|
||||
* their own entities, a uid = '22' condition will be built and applied.
|
||||
*
|
||||
* The following query types are supported:
|
||||
* - Entity queries with the $entity_type_id . '_access' tag.
|
||||
* - Views queries.
|
||||
*/
|
||||
interface QueryAccessHandlerInterface {
|
||||
|
||||
/**
|
||||
* Gets the conditions for the given operation and user.
|
||||
*
|
||||
* The "entity.query_access.$entity_type_id" event is fired to allow
|
||||
* modules to alter the conditions.
|
||||
*
|
||||
* @param string $operation
|
||||
* The access operation. Usually one of "view", "update" or "delete".
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* The user for which to restrict access, or NULL
|
||||
* to assume the current user. Defaults to NULL.
|
||||
*
|
||||
* @return \Drupal\entity\QueryAccess\ConditionGroup
|
||||
* The conditions.
|
||||
*/
|
||||
public function getConditions($operation, AccountInterface $account = NULL);
|
||||
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\entity\QueryAccess;
|
||||
|
||||
/**
|
||||
* Controls query access based on the uncacheable entity permissions.
|
||||
*
|
||||
* @see \Drupal\entity\UncacheableEntityAccessControlHandler
|
||||
* @see \Drupal\entity\UncacheableEntityPermissionProvider
|
||||
*/
|
||||
class UncacheableQueryAccessHandler extends QueryAccessHandlerBase {}
|
||||
@@ -0,0 +1,251 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\entity\QueryAccess;
|
||||
|
||||
use Drupal\Core\Cache\CacheableMetadata;
|
||||
use Drupal\Core\Database\Connection;
|
||||
use Drupal\Core\Database\Query\Condition as SqlCondition;
|
||||
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
|
||||
use Drupal\Core\Entity\EntityFieldManagerInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Entity\Sql\DefaultTableMapping;
|
||||
use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
|
||||
use Drupal\Core\Render\RendererInterface;
|
||||
use Drupal\views\Plugin\views\query\Sql;
|
||||
use Drupal\views\ViewExecutable;
|
||||
use Drupal\views\Views;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
|
||||
/**
|
||||
* Defines a class for altering views queries.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class ViewsQueryAlter implements ContainerInjectionInterface {
|
||||
|
||||
/**
|
||||
* The database connection.
|
||||
*
|
||||
* @var \Drupal\Core\Database\Connection
|
||||
*/
|
||||
protected $connection;
|
||||
|
||||
/**
|
||||
* The entity field manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityFieldManagerInterface
|
||||
*/
|
||||
protected $entityFieldManager;
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* The renderer.
|
||||
*
|
||||
* @var \Drupal\Core\Render\RendererInterface
|
||||
*/
|
||||
protected $renderer;
|
||||
|
||||
/**
|
||||
* The request stack.
|
||||
*
|
||||
* @var \Symfony\Component\HttpFoundation\RequestStack
|
||||
*/
|
||||
protected $requestStack;
|
||||
|
||||
/**
|
||||
* Constructs a new ViewsQueryAlter object.
|
||||
*
|
||||
* @param \Drupal\Core\Database\Connection $connection
|
||||
* The database connection.
|
||||
* @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
|
||||
* The entity field manager.
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
* @param \Drupal\Core\Render\RendererInterface $renderer
|
||||
* The renderer.
|
||||
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
|
||||
* The request stack.
|
||||
*/
|
||||
public function __construct(Connection $connection, EntityFieldManagerInterface $entity_field_manager, EntityTypeManagerInterface $entity_type_manager, RendererInterface $renderer, RequestStack $request_stack) {
|
||||
$this->connection = $connection;
|
||||
$this->entityFieldManager = $entity_field_manager;
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->renderer = $renderer;
|
||||
$this->requestStack = $request_stack;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('database'),
|
||||
$container->get('entity_field.manager'),
|
||||
$container->get('entity_type.manager'),
|
||||
$container->get('renderer'),
|
||||
$container->get('request_stack')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Alters the given views query.
|
||||
*
|
||||
* @param \Drupal\views\Plugin\views\query\Sql $query
|
||||
* The views query.
|
||||
* @param \Drupal\views\ViewExecutable $view
|
||||
* The view.
|
||||
*/
|
||||
public function alter(Sql $query, ViewExecutable $view) {
|
||||
$table_info = $query->getEntityTableInfo();
|
||||
$base_table = reset($table_info);
|
||||
if (empty($base_table['entity_type']) || $base_table['relationship_id'] != 'none') {
|
||||
return;
|
||||
}
|
||||
$entity_type_id = $base_table['entity_type'];
|
||||
$entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
|
||||
if (!$entity_type->hasHandlerClass('query_access')) {
|
||||
return;
|
||||
}
|
||||
$storage = $this->entityTypeManager->getStorage($entity_type_id);
|
||||
if (!$storage instanceof SqlContentEntityStorage) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var \Drupal\entity\QueryAccess\QueryAccessHandlerInterface $query_access */
|
||||
$query_access = $this->entityTypeManager->getHandler($entity_type_id, 'query_access');
|
||||
$conditions = $query_access->getConditions('view');
|
||||
if ($conditions->isAlwaysFalse()) {
|
||||
$query->addWhereExpression(0, '1 = 0');
|
||||
}
|
||||
elseif (count($conditions)) {
|
||||
// Store the data table, in case mapConditions() needs to join it in.
|
||||
$base_table['data_table'] = $entity_type->getDataTable();
|
||||
$field_storage_definitions = $this->entityFieldManager->getFieldStorageDefinitions($entity_type_id);
|
||||
/** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */
|
||||
$table_mapping = $storage->getTableMapping();
|
||||
$sql_conditions = $this->mapConditions($conditions, $query, $base_table, $field_storage_definitions, $table_mapping);
|
||||
$query->addWhere(0, $sql_conditions);
|
||||
}
|
||||
|
||||
$this->applyCacheability(CacheableMetadata::createFromObject($conditions));
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps an entity type's access conditions to views SQL conditions.
|
||||
*
|
||||
* @param \Drupal\entity\QueryAccess\ConditionGroup $conditions
|
||||
* The access conditions.
|
||||
* @param \Drupal\views\Plugin\views\query\Sql $query
|
||||
* The views query.
|
||||
* @param array $base_table
|
||||
* The base table information.
|
||||
* @param \Drupal\Core\Field\FieldStorageDefinitionInterface[] $field_storage_definitions
|
||||
* The field storage definitions.
|
||||
* @param \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping
|
||||
* The table mapping.
|
||||
*
|
||||
* @return \Drupal\Core\Database\Query\ConditionInterface
|
||||
* The SQL conditions.
|
||||
*/
|
||||
protected function mapConditions(ConditionGroup $conditions, Sql $query, array $base_table, array $field_storage_definitions, DefaultTableMapping $table_mapping) {
|
||||
$sql_condition = new SqlCondition($conditions->getConjunction());
|
||||
foreach ($conditions->getConditions() as $condition) {
|
||||
if ($condition instanceof ConditionGroup) {
|
||||
$nested_sql_conditions = $this->mapConditions($condition, $query, $base_table, $field_storage_definitions, $table_mapping);
|
||||
$sql_condition->condition($nested_sql_conditions);
|
||||
}
|
||||
else {
|
||||
$field = $condition->getField();
|
||||
$property_name = NULL;
|
||||
if (strpos($field, '.') !== FALSE) {
|
||||
list($field, $property_name) = explode('.', $field);
|
||||
}
|
||||
// Skip unknown fields.
|
||||
if (!isset($field_storage_definitions[$field])) {
|
||||
continue;
|
||||
}
|
||||
$field_storage_definition = $field_storage_definitions[$field];
|
||||
if (!$property_name) {
|
||||
$property_name = $field_storage_definition->getMainPropertyName();
|
||||
}
|
||||
|
||||
$column = $table_mapping->getFieldColumnName($field_storage_definition, $property_name);
|
||||
if ($table_mapping->requiresDedicatedTableStorage($field_storage_definitions[$field])) {
|
||||
if ($base_table['revision']) {
|
||||
$dedicated_table = $table_mapping->getDedicatedRevisionTableName($field_storage_definition);
|
||||
}
|
||||
else {
|
||||
$dedicated_table = $table_mapping->getDedicatedDataTableName($field_storage_definition);
|
||||
}
|
||||
// Views defaults to LEFT JOIN. For simplicity, we don't try to
|
||||
// use an INNER JOIN when it's safe to do so (AND conjunctions).
|
||||
$alias = $query->ensureTable($dedicated_table);
|
||||
}
|
||||
elseif ($base_table['revision'] && !$field_storage_definition->isRevisionable()) {
|
||||
// Workaround for #2652652, which causes $query->ensureTable()
|
||||
// to not work in this case, due to a missing relationship.
|
||||
if ($data_table = $query->getTableInfo($base_table['data_table'])) {
|
||||
$alias = $data_table['alias'];
|
||||
}
|
||||
else {
|
||||
$configuration = [
|
||||
'type' => 'INNER',
|
||||
'table' => $base_table['data_table'],
|
||||
'field' => 'id',
|
||||
'left_table' => $base_table['alias'],
|
||||
'left_field' => 'id',
|
||||
];
|
||||
/** @var \Drupal\Views\Plugin\views\join\JoinPluginBase $join */
|
||||
$join = Views::pluginManager('join')->createInstance('standard', $configuration);
|
||||
$alias = $query->addRelationship($base_table['data_table'], $join, $data_table);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$alias = $base_table['alias'];
|
||||
}
|
||||
|
||||
$value = $condition->getValue();
|
||||
$operator = $condition->getOperator();
|
||||
// Using LIKE/NOT LIKE ensures a case insensitive comparison.
|
||||
// @see \Drupal\Core\Entity\Query\Sql\Condition::translateCondition().
|
||||
$property_definitions = $field_storage_definition->getPropertyDefinitions();
|
||||
$case_sensitive = $property_definitions[$property_name]->getSetting('case_sensitive');
|
||||
$operator_map = [
|
||||
'=' => 'LIKE',
|
||||
'<>' => 'NOT LIKE',
|
||||
];
|
||||
if (!$case_sensitive && isset($operator_map[$operator])) {
|
||||
$operator = $operator_map[$operator];
|
||||
$value = $this->connection->escapeLike($value);
|
||||
}
|
||||
|
||||
$sql_condition->condition("$alias.$column", $value, $operator);
|
||||
}
|
||||
}
|
||||
|
||||
return $sql_condition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the cacheablity metadata to the current request.
|
||||
*
|
||||
* @param \Drupal\Core\Cache\CacheableMetadata $cacheable_metadata
|
||||
* The cacheability metadata.
|
||||
*/
|
||||
protected function applyCacheability(CacheableMetadata $cacheable_metadata) {
|
||||
$request = $this->requestStack->getCurrentRequest();
|
||||
if ($request->isMethodCacheable() && $this->renderer->hasRenderContext()) {
|
||||
$build = [];
|
||||
$cacheable_metadata->applyTo($build);
|
||||
$this->renderer->render($build);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -9,6 +9,10 @@ use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
/**
|
||||
* Provides the HTML route for deleting multiple entities.
|
||||
*
|
||||
* @deprecated Since Drupal 8.6.x the core DefaultHtmlRouteProvider provides
|
||||
* the route for any entity type with a "delete-multiple-form" link template
|
||||
* and a "delete-multiple-confirm" form.
|
||||
*/
|
||||
class DeleteMultipleRouteProvider implements EntityRouteProviderInterface {
|
||||
|
||||
@@ -34,7 +38,10 @@ class DeleteMultipleRouteProvider implements EntityRouteProviderInterface {
|
||||
* The generated route, if available.
|
||||
*/
|
||||
protected function deleteMultipleFormRoute(EntityTypeInterface $entity_type) {
|
||||
if ($entity_type->hasLinkTemplate('delete-multiple-form')) {
|
||||
// Core requires a "delete-multiple-confirm" form to be declared as well,
|
||||
// if it's missing, it's safe to assume that the entity type is still
|
||||
// relying on previous Entity API contrib behavior.
|
||||
if ($entity_type->hasLinkTemplate('delete-multiple-form') && !$entity_type->hasHandlerClass('form', 'delete-multiple-confirm')) {
|
||||
$route = new Route($entity_type->getLinkTemplate('delete-multiple-form'));
|
||||
$route->setDefault('_form', '\Drupal\entity\Form\DeleteMultipleForm');
|
||||
$route->setDefault('entity_type_id', $entity_type->id());
|
||||
|
||||
@@ -14,9 +14,8 @@ use Drupal\user\EntityOwnerInterface;
|
||||
* Provided permissions:
|
||||
* - administer $entity_type
|
||||
* - access $entity_type overview
|
||||
* - view any ($bundle) $entity_type
|
||||
* - view own ($bundle) $entity_type
|
||||
* - view own unpublished $entity_type
|
||||
* - view (own|any) ($bundle) $entity_type
|
||||
* - update (own|any) ($bundle) $entity_type
|
||||
* - delete (own|any) ($bundle) $entity_type
|
||||
* - create $bundle $entity_type
|
||||
@@ -69,8 +68,8 @@ class UncacheableEntityPermissionProvider extends EntityPermissionProviderBase {
|
||||
];
|
||||
}
|
||||
else {
|
||||
$permissions["view any {$entity_type_id}"] = [
|
||||
'title' => $this->t('View any @type', [
|
||||
$permissions["view {$entity_type_id}"] = [
|
||||
'title' => $this->t('View @type', [
|
||||
'@type' => $plural_label,
|
||||
]),
|
||||
];
|
||||
@@ -95,18 +94,25 @@ class UncacheableEntityPermissionProvider extends EntityPermissionProviderBase {
|
||||
$has_owner = $entity_type->entityClassImplements(EntityOwnerInterface::class);
|
||||
$plural_label = $entity_type->getPluralLabel();
|
||||
|
||||
$permissions["view any {$entity_type_id}"] = [
|
||||
'title' => $this->t('View any @type', [
|
||||
'@type' => $plural_label,
|
||||
]),
|
||||
];
|
||||
if ($has_owner) {
|
||||
$permissions["view any {$entity_type_id}"] = [
|
||||
'title' => $this->t('View any @type', [
|
||||
'@type' => $plural_label,
|
||||
]),
|
||||
];
|
||||
$permissions["view own {$entity_type_id}"] = [
|
||||
'title' => $this->t('View own @type', [
|
||||
'@type' => $plural_label,
|
||||
]),
|
||||
];
|
||||
}
|
||||
else {
|
||||
$permissions["view {$entity_type_id}"] = [
|
||||
'title' => $this->t('View @type', [
|
||||
'@type' => $plural_label,
|
||||
]),
|
||||
];
|
||||
}
|
||||
|
||||
foreach ($bundles as $bundle_name => $bundle_info) {
|
||||
if ($has_owner) {
|
||||
@@ -124,8 +130,8 @@ class UncacheableEntityPermissionProvider extends EntityPermissionProviderBase {
|
||||
];
|
||||
}
|
||||
else {
|
||||
$permissions["view any {$bundle_name} {$entity_type_id}"] = [
|
||||
'title' => $this->t('@bundle: View any @type', [
|
||||
$permissions["view {$bundle_name} {$entity_type_id}"] = [
|
||||
'title' => $this->t('@bundle: View @type', [
|
||||
'@bundle' => $bundle_info['label'],
|
||||
'@type' => $plural_label,
|
||||
]),
|
||||
|
||||
Reference in New Issue
Block a user