updated contrib modules

This commit is contained in:
Bachir Soussi Chiadmi
2017-11-14 16:12:58 +01:00
parent c2b4e25be4
commit b32fe9ef14
317 changed files with 4672 additions and 13347 deletions
@@ -7,9 +7,8 @@ use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Routing\Access\AccessInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Routing\Route;
/**
@@ -30,42 +29,42 @@ class EntityRevisionRouteAccessChecker implements AccessInterface {
protected $accessCache = array();
/**
* The request stack.
* The currently active route match object.
*
* @var \Symfony\Component\HttpFoundation\RequestStack
* @var \Drupal\Core\Routing\RouteMatchInterface
*/
protected $requestStack;
protected $routeMatch;
/**
* Creates a new EntityRevisionRouteAccessChecker instance.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity manager.
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
* The request stack.
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* The currently active route match object.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager, RequestStack $request_stack) {
public function __construct(EntityTypeManagerInterface $entity_type_manager, RouteMatchInterface $route_match) {
$this->entityTypeManager = $entity_type_manager;
$this->requestStack = $request_stack;
$this->routeMatch = $route_match;
}
/**
* {@inheritdoc}
*/
public function access(Route $route, AccountInterface $account, Request $request = NULL) {
if (empty($request)) {
$request = $this->requestStack->getCurrentRequest();
public function access(Route $route, AccountInterface $account, RouteMatchInterface $route_match = NULL) {
if (empty($route_match)) {
$route_match = $this->routeMatch;
}
$operation = $route->getRequirement('_entity_access_revision');
list(, $operation) = explode('.', $operation, 2);
list($entity_type_id, $operation) = explode('.', $operation, 2);
if ($operation === 'list') {
$_entity = $request->attributes->get('_entity', $request->attributes->get($route->getOption('entity_type_id')));
$_entity = $route_match->getParameter($entity_type_id);
return AccessResult::allowedIf($this->checkAccess($_entity, $account, $operation))->cachePerPermissions();
}
else {
$_entity_revision = $request->attributes->get('_entity_revision');
$_entity_revision = $route_match->getParameter($entity_type_id . '_revision');
return AccessResult::allowedIf($_entity_revision && $this->checkAccess($_entity_revision, $account, $operation))->cachePerPermissions();
}
}
@@ -104,8 +103,10 @@ class EntityRevisionRouteAccessChecker implements AccessInterface {
$cid = $entity->getRevisionId() . ':' . $langcode . ':' . $account->id() . ':' . $operation;
if (!isset($this->accessCache[$cid])) {
$admin_permission = $entity_type->getAdminPermission();
// Perform basic permission checks first.
if (!$account->hasPermission($map[$operation]) && !$account->hasPermission($type_map[$operation]) && !$account->hasPermission('administer nodes')) {
if (!$account->hasPermission($map[$operation]) && !$account->hasPermission($type_map[$operation]) && ($admin_permission && !$account->hasPermission($admin_permission))) {
$this->accessCache[$cid] = FALSE;
return FALSE;
}
@@ -114,6 +115,8 @@ class EntityRevisionRouteAccessChecker implements AccessInterface {
$this->accessCache[$cid] = TRUE;
}
else {
// Entity access handlers are generally not aware of the "list" operation.
$operation = $operation == 'list' ? 'view' : $operation;
// First check the access to the default revision and finally, if the
// node passed in is not the default revision then access to that, too.
$this->accessCache[$cid] = $entity_access->access($entity_storage->load($entity->id()), $operation, $account) && ($entity->isDefaultRevision() || $entity_access->access($entity, $operation, $account));
@@ -0,0 +1,27 @@
<?php
namespace Drupal\entity;
use Drupal\Core\Field\BaseFieldDefinition;
/**
* Provides a field definition class for bundle fields.
*
* Core currently doesn't provide one, the hook_entity_bundle_field_info()
* example uses BaseFieldDefinition, which is wrong. Tracked in #2346347.
*
* Note that this class implements both FieldStorageDefinitionInterface and
* FieldDefinitionInterface. This is a simplification for DX reasons,
* allowing code to return just the bundle definitions instead of having to
* return both storage definitions and bundle definitions.
*/
class BundleFieldDefinition extends BaseFieldDefinition {
/**
* {@inheritdoc}
*/
public function isBaseField() {
return FALSE;
}
}
@@ -0,0 +1,102 @@
<?php
namespace Drupal\entity\BundlePlugin;
use Drupal\Component\Plugin\PluginManagerInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
class BundlePluginHandler implements BundlePluginHandlerInterface {
/**
* The entity type.
*
* @var \Drupal\Core\Entity\EntityTypeInterface
*/
protected $entityType;
/**
* The bundle plugin manager.
*
* @var \Drupal\Component\Plugin\PluginManagerInterface
*/
protected $pluginManager;
/**
* Constructs a new BundlePluginHandler object.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type.
* @param \Drupal\Component\Plugin\PluginManagerInterface $plugin_manager
* The bundle plugin manager.
*/
public function __construct(EntityTypeInterface $entity_type, PluginManagerInterface $plugin_manager) {
$this->entityType = $entity_type;
$this->pluginManager = $plugin_manager;
}
/**
* {@inheritdoc}
*/
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
return new static(
$entity_type,
$container->get('plugin.manager.' . $entity_type->get('bundle_plugin_type'))
);
}
/**
* {@inheritdoc}
*/
public function getBundleInfo() {
$bundles = [];
foreach ($this->pluginManager->getDefinitions() as $plugin_id => $definition) {
$bundles[$plugin_id] = [
'label' => $definition['label'],
'description' => isset($definition['description']) ? $definition['description'] : '',
'translatable' => $this->entityType->isTranslatable(),
'provider' => $definition['provider'],
];
}
return $bundles;
}
/**
* {@inheritdoc}
*/
public function getFieldStorageDefinitions() {
$definitions = [];
foreach (array_keys($this->pluginManager->getDefinitions()) as $plugin_id) {
/** @var \Drupal\entity\BundlePlugin\BundlePluginInterface $plugin */
$plugin = $this->pluginManager->createInstance($plugin_id);
$definitions += $plugin->buildFieldDefinitions();
}
// Ensure the presence of required keys which aren't set by the plugin.
foreach ($definitions as $field_name => $definition) {
$definition->setName($field_name);
$definition->setTargetEntityTypeId($this->entityType->id());
$definitions[$field_name] = $definition;
}
return $definitions;
}
/**
* {@inheritdoc}
*/
public function getFieldDefinitions($bundle) {
/** @var \Drupal\entity\BundlePlugin\BundlePluginInterface $plugin */
$plugin = $this->pluginManager->createInstance($bundle);
$definitions = $plugin->buildFieldDefinitions();
// Ensure the presence of required keys which aren't set by the plugin.
foreach ($definitions as $field_name => $definition) {
$definition->setName($field_name);
$definition->setTargetEntityTypeId($this->entityType->id());
$definition->setTargetBundle($bundle);
$definitions[$field_name] = $definition;
}
return $definitions;
}
}
@@ -0,0 +1,37 @@
<?php
namespace Drupal\entity\BundlePlugin;
use Drupal\Core\Entity\EntityHandlerInterface;
/**
* Handles plugin-provided bundles.
*/
interface BundlePluginHandlerInterface extends EntityHandlerInterface {
/**
* Gets the bundle info.
*
* @return array
* An array of bundle information keyed by the bundle name.
* The format expected by hook_entity_bundle_info().
*/
public function getBundleInfo();
/**
* Gets the field storage definitions.
*/
public function getFieldStorageDefinitions();
/**
* Gets the field definitions for a specific bundle.
*
* @param string $bundle
* The bundle name.
*
* @return \Drupal\entity\BundleFieldDefinition[]
* An array of bundle field definitions, keyed by field name.
*/
public function getFieldDefinitions($bundle);
}
@@ -0,0 +1,94 @@
<?php
namespace Drupal\entity\BundlePlugin;
use Drupal\Core\Entity\EntityBundleListenerInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Field\FieldDefinitionListenerInterface;
use Drupal\Core\Field\FieldStorageDefinitionListenerInterface;
class BundlePluginInstaller implements BundlePluginInstallerInterface {
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The entity bundle listener.
*
* @var \Drupal\Core\Entity\EntityBundleListenerInterface
*/
protected $entityBundleListener;
/**
* The field storage definition listener.
*
* @var \Drupal\Core\Field\FieldStorageDefinitionListenerInterface
*/
protected $fieldStorageDefinitionListener;
/**
* The field definition listener.
*
* @var \Drupal\Core\Field\FieldDefinitionListenerInterface
*/
protected $fieldDefinitionListener;
/**
* Constructs a new BundlePluginInstaller object.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\Core\Entity\EntityBundleListenerInterface $entity_bundle_listener
* The entity bundle listener.
* @param \Drupal\Core\Field\FieldStorageDefinitionListenerInterface $field_storage_definition_listener
* The field storage definition listener.
* @param \Drupal\Core\Field\FieldDefinitionListenerInterface $field_definition_listener
* The field definition listener.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager, EntityBundleListenerInterface $entity_bundle_listener, FieldStorageDefinitionListenerInterface $field_storage_definition_listener, FieldDefinitionListenerInterface $field_definition_listener) {
$this->entityTypeManager = $entity_type_manager;
$this->entityBundleListener = $entity_bundle_listener;
$this->fieldStorageDefinitionListener = $field_storage_definition_listener;
$this->fieldDefinitionListener = $field_definition_listener;
}
/**
* {@inheritdoc}
*/
public function installBundles(EntityTypeInterface $entity_type, array $modules) {
$bundle_handler = $this->entityTypeManager->getHandler($entity_type->id(), 'bundle_plugin');
$bundles = array_filter($bundle_handler->getBundleInfo(), function ($bundle_info) use ($modules) {
return in_array($bundle_info['provider'], $modules, TRUE);
});
foreach (array_keys($bundles) as $bundle) {
$this->entityBundleListener->onBundleCreate($bundle, $entity_type->id());
foreach ($bundle_handler->getFieldDefinitions($bundle) as $definition) {
$this->fieldStorageDefinitionListener->onFieldStorageDefinitionCreate($definition);
$this->fieldDefinitionListener->onFieldDefinitionCreate($definition);
}
}
}
/**
* {@inheritdoc}
*/
public function uninstallBundles(EntityTypeInterface $entity_type, array $modules) {
$bundle_handler = $this->entityTypeManager->getHandler($entity_type->id(), 'bundle_plugin');
$bundles = array_filter($bundle_handler->getBundleInfo(), function ($bundle_info) use ($modules) {
return in_array($bundle_info['provider'], $modules, TRUE);
});
foreach (array_keys($bundles) as $bundle) {
$this->entityBundleListener->onBundleDelete($bundle, $entity_type->id());
foreach ($bundle_handler->getFieldDefinitions($bundle) as $definition) {
$this->fieldDefinitionListener->onFieldDefinitionDelete($definition);
$this->fieldStorageDefinitionListener->onFieldStorageDefinitionDelete($definition);
}
}
}
}
@@ -0,0 +1,34 @@
<?php
namespace Drupal\entity\BundlePlugin;
use Drupal\Core\Entity\EntityTypeInterface;
/**
* Installs and uninstalls bundle plugins.
*
* Ensures that the fields provided by the bundle plugins are created/deleted.
*/
interface BundlePluginInstallerInterface {
/**
* Installs the bundle plugins provided by the specified modules.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type.
* @param array $modules
* The modules.
*/
public function installBundles(EntityTypeInterface $entity_type, array $modules);
/**
* Uninstalls the bundle plugins provided by the specified modules.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type.
* @param array $modules
* The modules.
*/
public function uninstallBundles(EntityTypeInterface $entity_type, array $modules);
}
@@ -0,0 +1,24 @@
<?php
namespace Drupal\entity\BundlePlugin;
use Drupal\Component\Plugin\PluginInspectionInterface;
/**
* Interface for plugins which act as entity bundles.
*/
interface BundlePluginInterface extends PluginInspectionInterface {
/**
* Builds the field definitions for entities of this bundle.
*
* Important:
* Field names must be unique across all bundles.
* It is recommended to prefix them with the bundle name (plugin ID).
*
* @return \Drupal\entity\BundleFieldDefinition[]
* An array of bundle field definitions, keyed by field name.
*/
public function buildFieldDefinitions();
}
@@ -0,0 +1,76 @@
<?php
namespace Drupal\entity\BundlePlugin;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\ModuleUninstallValidatorInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslationInterface;
/**
* Prevents uninstalling modules with bundle plugins in case of found data.
*/
class BundlePluginUninstallValidator implements ModuleUninstallValidatorInterface {
use StringTranslationTrait;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* Constructs the object.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
* The string translation service.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager, TranslationInterface $string_translation) {
$this->entityTypeManager = $entity_type_manager;
$this->stringTranslation = $string_translation;
}
/**
* {@inheritdoc}
*/
public function validate($module) {
$reasons = [];
foreach (entity_get_bundle_plugin_entity_types() as $entity_type) {
/** @var \Drupal\entity\BundlePlugin\BundlePluginHandler $bundle_handler */
$bundle_handler = $this->entityTypeManager->getHandler($entity_type->id(), 'bundle_plugin');
$bundles = $bundle_handler->getBundleInfo();
// We find all bundles which have to be removed due to the uninstallation.
$bundles_filtered_by_module = array_filter($bundles, function ($bundle_info) use ($module) {
return $module === $bundle_info['provider'];
});
if (!empty($bundles_filtered_by_module)) {
$bundle_keys_with_content = array_filter(array_keys($bundles_filtered_by_module), function ($bundle) use ($entity_type) {
$result = $this->entityTypeManager->getStorage($entity_type->id())->getQuery()
->condition($entity_type->getKey('bundle'), $bundle)
->range(0, 1)
->execute();
return !empty($result);
});
$bundles_with_content = array_intersect_key($bundles_filtered_by_module, array_flip($bundle_keys_with_content));
foreach ($bundles_with_content as $bundle) {
$reasons[] = $this->t('There is data for the bundle @bundle on the entity type @entity_type. Please remove all content before uninstalling the module.', [
'@bundle' => $bundle['label'],
'@entity_type' => $entity_type->getLabel(),
]);
}
}
}
return $reasons;
}
}
@@ -11,7 +11,6 @@ use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Entity\RevisionLogInterface;
use Drupal\user\EntityOwnerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
@@ -5,16 +5,30 @@ namespace Drupal\entity;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\EntityAccessControlHandler as CoreEntityAccessControlHandler;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityPublishedInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\user\EntityOwnerInterface;
/**
* Controls access based on the generic entity permissions.
*
* @see \Drupal\entity\EntityPermissionProvider
* @see \Drupal\entity\UncacheableEntityPermissionProvider
*/
class EntityAccessControlHandler extends CoreEntityAccessControlHandler {
/**
* {@inheritdoc}
*/
public function __construct(EntityTypeInterface $entity_type) {
parent::__construct($entity_type);
if (!$entity_type->hasHandlerClass('permission_provider') || !is_a($entity_type->getHandlerClass('permission_provider'), EntityPermissionProvider::class, TRUE)) {
throw new \Exception("This entity access control handler requires the entity permissions provider: {EntityPermissionProvider::class}");
}
}
/**
* {@inheritdoc}
*/
@@ -51,10 +65,18 @@ class EntityAccessControlHandler extends CoreEntityAccessControlHandler {
* The access result.
*/
protected function checkEntityPermissions(EntityInterface $entity, $operation, AccountInterface $account) {
return AccessResult::allowedIfHasPermissions($account, [
"$operation {$entity->getEntityTypeId()}",
"$operation {$entity->bundle()} {$entity->getEntityTypeId()}",
], 'OR');
if ($operation === 'view') {
$permissions = [
"view {$entity->getEntityTypeId()}"
];
}
else {
$permissions = [
"$operation {$entity->getEntityTypeId()}",
"$operation {$entity->bundle()} {$entity->getEntityTypeId()}",
];
}
return AccessResult::allowedIfHasPermissions($account, $permissions, 'OR');
}
/**
@@ -72,23 +94,39 @@ class EntityAccessControlHandler extends CoreEntityAccessControlHandler {
* The access result.
*/
protected function checkEntityOwnerPermissions(EntityInterface $entity, $operation, AccountInterface $account) {
/** @var \Drupal\Core\Entity\EntityInterface|\Drupal\user\EntityOwnerInterface $entity */
if (($account->id() == $entity->getOwnerId())) {
$result = AccessResult::allowedIfHasPermissions($account, [
"$operation own {$entity->getEntityTypeId()}",
"$operation any {$entity->getEntityTypeId()}",
"$operation own {$entity->bundle()} {$entity->getEntityTypeId()}",
"$operation any {$entity->bundle()} {$entity->getEntityTypeId()}",
], 'OR');
if ($operation === 'view') {
if ($entity instanceof EntityPublishedInterface && !$entity->isPublished()) {
if (($account->id() == $entity->getOwnerId())) {
$permissions = [
"view own unpublished {$entity->getEntityTypeId()}",
];
return AccessResult::allowedIfHasPermissions($account, $permissions)->cachePerUser();
}
return AccessResult::neutral()->cachePerUser();
}
else {
return AccessResult::allowedIfHasPermissions($account, [
"view {$entity->getEntityTypeId()}",
]);
}
}
else {
$result = AccessResult::allowedIfHasPermissions($account, [
"$operation any {$entity->getEntityTypeId()}",
"$operation any {$entity->bundle()} {$entity->getEntityTypeId()}",
], 'OR');
if (($account->id() == $entity->getOwnerId())) {
$result = AccessResult::allowedIfHasPermissions($account, [
"$operation own {$entity->getEntityTypeId()}",
"$operation any {$entity->getEntityTypeId()}",
"$operation own {$entity->bundle()} {$entity->getEntityTypeId()}",
"$operation any {$entity->bundle()} {$entity->getEntityTypeId()}",
], 'OR');
}
else {
$result = AccessResult::allowedIfHasPermissions($account, [
"$operation any {$entity->getEntityTypeId()}",
"$operation any {$entity->bundle()} {$entity->getEntityTypeId()}",
], 'OR');
}
return $result;
}
return $result->cachePerUser();
}
/**
@@ -2,8 +2,8 @@
namespace Drupal\entity;
use Drupal\Core\Entity\ContentEntityTypeInterface;
use Drupal\Core\Entity\EntityHandlerInterface;
use Drupal\Core\Entity\EntityPublishedInterface;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
@@ -11,10 +11,21 @@ use Drupal\user\EntityOwnerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides generic entity permissions.
* Provides generic entity permissions which are still cacheable.
*
* Supports both entity_type and bundle granularities.
* Supports entity ownership (own/any permissions).
* This includes:
*
* - administer $entity_type
* - access $entity_type overview
* - view $entity_type
* - view own unpublished $entity_type
* - update (own|any) ($bundle) $entity_type
* - delete (own|any) ($bundle) $entity_type
* - create $bundle $entity_type
*
* This class does not support "view own ($bundle) $entity_type", because this
* results in caching per user. If you need this use case, please use
* \Drupal\entity\UncacheableEntityPermissionProvider instead.
*
* Intended for content entity types, since config entity types usually rely
* on a single "administer" permission.
@@ -29,218 +40,26 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
* @see \Drupal\entity\EntityAccessControlHandler
* @see \Drupal\entity\EntityPermissions
*/
class EntityPermissionProvider implements EntityPermissionProviderInterface, EntityHandlerInterface {
use StringTranslationTrait;
/**
* The entity type bundle info.
*
* @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
*/
protected $entityTypeBundleInfo;
/**
* Constructs a new EntityPermissionProvider object.
*
* @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info
* The entity type bundle info.
*/
public function __construct(EntityTypeBundleInfoInterface $entity_type_bundle_info) {
$this->entityTypeBundleInfo = $entity_type_bundle_info;
}
/**
* {@inheritdoc}
*/
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
return new static(
$container->get('entity_type.bundle.info')
);
}
class EntityPermissionProvider extends EntityPermissionProviderBase {
/**
* {@inheritdoc}
*/
public function buildPermissions(EntityTypeInterface $entity_type) {
$entity_type_id = $entity_type->id();
$has_owner = $entity_type->isSubclassOf(EntityOwnerInterface::class);
$singular_label = $entity_type->getSingularLabel();
$plural_label = $entity_type->getPluralLabel();
$permissions = [];
$permissions["administer {$entity_type_id}"] = [
'title' => $this->t('Administer @type', ['@type' => $plural_label]),
'restrict access' => TRUE,
];
$permissions["access {$entity_type_id} overview"] = [
'title' => $this->t('Access the @type overview page', ['@type' => $plural_label]),
];
$permissions = parent::buildPermissions($entity_type);
// View permissions are the same for both granularities.
if ($has_owner) {
$permissions["view any {$entity_type_id}"] = [
'title' => $this->t('View any @type', [
'@type' => $singular_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,
]),
];
}
// Generate the other permissions based on granularity.
if ($entity_type->getPermissionGranularity() == 'entity_type') {
$permissions += $this->buildEntityTypePermissions($entity_type);
}
else {
$permissions += $this->buildBundlePermissions($entity_type);
}
foreach ($permissions as $name => $permission) {
// Permissions are grouped by provider on admin/people/permissions.
$permissions[$name]['provider'] = $entity_type->getProvider();
// TranslatableMarkup objects don't sort properly.
$permissions[$name]['title'] = (string) $permission['title'];
}
return $permissions;
}
/**
* Builds permissions for the entity_type granularity.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type.
*
* @return array
* The permissions.
*/
protected function buildEntityTypePermissions(EntityTypeInterface $entity_type) {
$entity_type_id = $entity_type->id();
$has_owner = $entity_type->isSubclassOf(EntityOwnerInterface::class);
$singular_label = $entity_type->getSingularLabel();
$plural_label = $entity_type->getPluralLabel();
$permissions = [];
$permissions["create {$entity_type_id}"] = [
'title' => $this->t('Create @type', [
$permissions["view {$entity_type_id}"] = [
'title' => $this->t('View @type', [
'@type' => $plural_label,
]),
];
if ($has_owner) {
$permissions["update any {$entity_type_id}"] = [
'title' => $this->t('Update any @type', [
'@type' => $singular_label,
]),
];
$permissions["update own {$entity_type_id}"] = [
'title' => $this->t('Update own @type', [
'@type' => $plural_label,
]),
];
$permissions["delete any {$entity_type_id}"] = [
'title' => $this->t('Delete any @type', [
'@type' => $singular_label,
]),
];
$permissions["delete own {$entity_type_id}"] = [
'title' => $this->t('Delete own @type', [
'@type' => $plural_label,
]),
];
}
else {
$permissions["update {$entity_type_id}"] = [
'title' => $this->t('Update @type', [
'@type' => $plural_label,
]),
];
$permissions["delete {$entity_type_id}"] = [
'title' => $this->t('Delete @type', [
'@type' => $plural_label,
]),
];
}
return $permissions;
return $this->processPermissions($permissions, $entity_type);
}
/**
* Builds permissions for the bundle granularity.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type.
*
* @return array
* The permissions.
*/
protected function buildBundlePermissions(EntityTypeInterface $entity_type) {
$entity_type_id = $entity_type->id();
$bundles = $this->entityTypeBundleInfo->getBundleInfo($entity_type_id);
$has_owner = $entity_type->isSubclassOf(EntityOwnerInterface::class);
$singular_label = $entity_type->getSingularLabel();
$plural_label = $entity_type->getPluralLabel();
$permissions = [];
foreach ($bundles as $bundle_name => $bundle_info) {
$permissions["create {$bundle_name} {$entity_type_id}"] = [
'title' => $this->t('@bundle: Create @type', [
'@bundle' => $bundle_info['label'],
'@type' => $plural_label,
]),
];
if ($has_owner) {
$permissions["update any {$bundle_name} {$entity_type_id}"] = [
'title' => $this->t('@bundle: Update any @type', [
'@bundle' => $bundle_info['label'],
'@type' => $singular_label,
]),
];
$permissions["update own {$bundle_name} {$entity_type_id}"] = [
'title' => $this->t('@bundle: Update own @type', [
'@bundle' => $bundle_info['label'],
'@type' => $plural_label,
]),
];
$permissions["delete any {$bundle_name} {$entity_type_id}"] = [
'title' => $this->t('@bundle: Delete any @type', [
'@bundle' => $bundle_info['label'],
'@type' => $singular_label,
]),
];
$permissions["delete own {$bundle_name} {$entity_type_id}"] = [
'title' => $this->t('@bundle: Delete own @type', [
'@bundle' => $bundle_info['label'],
'@type' => $plural_label,
]),
];
}
else {
$permissions["update {$bundle_name} {$entity_type_id}"] = [
'title' => $this->t('@bundle: Update @type', [
'@bundle' => $bundle_info['label'],
'@type' => $plural_label,
]),
];
$permissions["delete {$bundle_name} {$entity_type_id}"] = [
'title' => $this->t('@bundle: Delete @type', [
'@bundle' => $bundle_info['label'],
'@type' => $plural_label,
]),
];
}
}
return $permissions;
}
}
@@ -0,0 +1,231 @@
<?php
namespace Drupal\entity;
use Drupal\Core\Entity\EntityHandlerInterface;
use Drupal\Core\Entity\EntityPublishedInterface;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\user\EntityOwnerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* @internal
*/
class EntityPermissionProviderBase implements EntityPermissionProviderInterface, EntityHandlerInterface {
use StringTranslationTrait;
/**
* The entity type bundle info.
*
* @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
*/
protected $entityTypeBundleInfo;
/**
* Constructs a new EntityPermissionProvider object.
*
* @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info
* The entity type bundle info.
*/
public function __construct(EntityTypeBundleInfoInterface $entity_type_bundle_info) {
$this->entityTypeBundleInfo = $entity_type_bundle_info;
}
/**
* {@inheritdoc}
*/
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
return new static(
$container->get('entity_type.bundle.info')
);
}
/**
* {@inheritdoc}
*/
public function buildPermissions(EntityTypeInterface $entity_type) {
$entity_type_id = $entity_type->id();
$has_owner = $entity_type->entityClassImplements(EntityOwnerInterface::class);
$plural_label = $entity_type->getPluralLabel();
$permissions = [];
$permissions["administer {$entity_type_id}"] = [
'title' => $this->t('Administer @type', ['@type' => $plural_label]),
'restrict access' => TRUE,
];
$permissions["access {$entity_type_id} overview"] = [
'title' => $this->t('Access the @type overview page', ['@type' => $plural_label]),
];
if ($has_owner && $entity_type->entityClassImplements(EntityPublishedInterface::class)) {
$permissions["view own unpublished {$entity_type_id}"] = [
'title' => $this->t('View own unpublished @type', [
'@type' => $plural_label,
]),
];
}
// Generate the other permissions based on granularity.
if ($entity_type->getPermissionGranularity() === 'entity_type') {
$permissions += $this->buildEntityTypePermissions($entity_type);
}
else {
$permissions += $this->buildBundlePermissions($entity_type);
}
return $this->processPermissions($permissions, $entity_type);
}
/**
* Adds the provider and converts the titles to strings to allow sorting.
*
* @param array $permissions
* The array of permissions
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type.
*
* @return array
* An array of processed permissions.
*/
protected function processPermissions(array $permissions, EntityTypeInterface $entity_type) {
foreach ($permissions as $name => $permission) {
// Permissions are grouped by provider on admin/people/permissions.
$permissions[$name]['provider'] = $entity_type->getProvider();
// TranslatableMarkup objects don't sort properly.
$permissions[$name]['title'] = (string) $permission['title'];
}
return $permissions;
}
/**
* Builds permissions for the entity_type granularity.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type.
*
* @return array
* The permissions.
*/
protected function buildEntityTypePermissions(EntityTypeInterface $entity_type) {
$entity_type_id = $entity_type->id();
$has_owner = $entity_type->entityClassImplements(EntityOwnerInterface::class);
$singular_label = $entity_type->getSingularLabel();
$plural_label = $entity_type->getPluralLabel();
$permissions = [];
$permissions["create {$entity_type_id}"] = [
'title' => $this->t('Create @type', [
'@type' => $plural_label,
]),
];
if ($has_owner) {
$permissions["update any {$entity_type_id}"] = [
'title' => $this->t('Update any @type', [
'@type' => $singular_label,
]),
];
$permissions["update own {$entity_type_id}"] = [
'title' => $this->t('Update own @type', [
'@type' => $plural_label,
]),
];
$permissions["delete any {$entity_type_id}"] = [
'title' => $this->t('Delete any @type', [
'@type' => $singular_label,
]),
];
$permissions["delete own {$entity_type_id}"] = [
'title' => $this->t('Delete own @type', [
'@type' => $plural_label,
]),
];
}
else {
$permissions["update {$entity_type_id}"] = [
'title' => $this->t('Update @type', [
'@type' => $plural_label,
]),
];
$permissions["delete {$entity_type_id}"] = [
'title' => $this->t('Delete @type', [
'@type' => $plural_label,
]),
];
}
return $permissions;
}
/**
* Builds permissions for the bundle granularity.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type.
*
* @return array
* The permissions.
*/
protected function buildBundlePermissions(EntityTypeInterface $entity_type) {
$entity_type_id = $entity_type->id();
$bundles = $this->entityTypeBundleInfo->getBundleInfo($entity_type_id);
$has_owner = $entity_type->entityClassImplements(EntityOwnerInterface::class);
$singular_label = $entity_type->getSingularLabel();
$plural_label = $entity_type->getPluralLabel();
$permissions = [];
foreach ($bundles as $bundle_name => $bundle_info) {
$permissions["create {$bundle_name} {$entity_type_id}"] = [
'title' => $this->t('@bundle: Create @type', [
'@bundle' => $bundle_info['label'],
'@type' => $plural_label,
]),
];
if ($has_owner) {
$permissions["update any {$bundle_name} {$entity_type_id}"] = [
'title' => $this->t('@bundle: Update any @type', [
'@bundle' => $bundle_info['label'],
'@type' => $singular_label,
]),
];
$permissions["update own {$bundle_name} {$entity_type_id}"] = [
'title' => $this->t('@bundle: Update own @type', [
'@bundle' => $bundle_info['label'],
'@type' => $plural_label,
]),
];
$permissions["delete any {$bundle_name} {$entity_type_id}"] = [
'title' => $this->t('@bundle: Delete any @type', [
'@bundle' => $bundle_info['label'],
'@type' => $singular_label,
]),
];
$permissions["delete own {$bundle_name} {$entity_type_id}"] = [
'title' => $this->t('@bundle: Delete own @type', [
'@bundle' => $bundle_info['label'],
'@type' => $plural_label,
]),
];
}
else {
$permissions["update {$bundle_name} {$entity_type_id}"] = [
'title' => $this->t('@bundle: Update @type', [
'@bundle' => $bundle_info['label'],
'@type' => $plural_label,
]),
];
$permissions["delete {$bundle_name} {$entity_type_id}"] = [
'title' => $this->t('@bundle: Delete @type', [
'@bundle' => $bundle_info['label'],
'@type' => $plural_label,
]),
];
}
}
return $permissions;
}
}
@@ -10,6 +10,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
* Generates entity permissions via their permission providers.
*
* @see \Drupal\entity\EntityPermissionProvider
* @see \Drupal\entity\UncacheableEntityPermissionProvider
*/
class EntityPermissions implements ContainerInjectionInterface {
@@ -69,7 +69,7 @@ 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->isSubclassOf(ContentEntityInterface::class) && $entity_type->hasLinkTemplate('delete-multiple-form');
return $entity_type->entityClassImplements(ContentEntityInterface::class) && $entity_type->hasLinkTemplate('delete-multiple-form');
});
return $entity_types;
@@ -3,6 +3,7 @@
namespace Drupal\entity\Plugin\Derivative;
use Drupal\Component\Plugin\Derivative\DeriverBase;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -24,7 +25,7 @@ class RevisionsOverviewDeriver extends DeriverBase implements ContainerDeriverIn
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
* The entity type manager.
*/
public function __construct(\Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager) {
public function __construct(EntityTypeManagerInterface $entityTypeManager) {
$this->entityTypeManager = $entityTypeManager;
}
@@ -55,7 +56,7 @@ class RevisionsOverviewDeriver extends DeriverBase implements ContainerDeriverIn
$this->derivatives[$entity_type_id] = [
'route_name' => "entity.$entity_type_id.version_history",
'title' => 'Revisions',
'title' => t('Revisions'),
'base_route' => "entity.$entity_type_id.canonical",
'weight' => 20,
] + $base_plugin_definition;
@@ -3,7 +3,6 @@
namespace Drupal\entity\Revision;
use Drupal\Core\Entity\RevisionableContentEntityBase as BaseRevisionableContentEntityBase;
use Drupal\Core\Entity\ContentEntityBase;
/**
* Improves the url route handling of core's revisionable content entity base.
@@ -0,0 +1,138 @@
<?php
namespace Drupal\entity;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\EntityAccessControlHandler as CoreEntityAccessControlHandler;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityPublishedInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\user\EntityOwnerInterface;
/**
* Controls access based on the uncacheable entity permissions.
*
* @see \Drupal\entity\UncacheableEntityPermissionProvider
*
* Note: this access control handler will cause pages to be cached per user.
*/
class UncacheableEntityAccessControlHandler extends CoreEntityAccessControlHandler {
/**
* {@inheritdoc}
*/
public function __construct(EntityTypeInterface $entity_type) {
parent::__construct($entity_type);
if (!$entity_type->hasHandlerClass('permission_provider') || !is_a($entity_type->getHandlerClass('permission_provider'), UncacheableEntityPermissionProvider::class, TRUE)) {
throw new \Exception("This entity access control handler requires the entity permissions provider: {EntityPermissionProvider::class}");
}
}
/**
* {@inheritdoc}
*/
protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) {
$account = $this->prepareUser($account);
/** @var \Drupal\Core\Access\AccessResult $result */
$result = parent::checkAccess($entity, $operation, $account);
if ($result->isNeutral()) {
if ($entity instanceof EntityOwnerInterface) {
$result = $this->checkEntityOwnerPermissions($entity, $operation, $account);
}
else {
$result = $this->checkEntityPermissions($entity, $operation, $account);
}
}
// Ensure that access is evaluated again when the entity changes.
return $result->addCacheableDependency($entity);
}
/**
* Checks the entity operation and bundle permissions.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity for which to check access.
* @param string $operation
* The entity operation. Usually one of 'view', 'view label', 'update' or
* 'delete'.
* @param \Drupal\Core\Session\AccountInterface $account
* The user for which to check access.
*
* @return \Drupal\Core\Access\AccessResultInterface
* The access result.
*/
protected function checkEntityPermissions(EntityInterface $entity, $operation, AccountInterface $account) {
return AccessResult::allowedIfHasPermissions($account, [
"$operation {$entity->getEntityTypeId()}",
"$operation {$entity->bundle()} {$entity->getEntityTypeId()}",
], 'OR');
}
/**
* Checks the entity operation and bundle permissions, with owners.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity for which to check access.
* @param string $operation
* The entity operation. Usually one of 'view', 'view label', 'update' or
* 'delete'.
* @param \Drupal\Core\Session\AccountInterface $account
* The user for which to check access.
*
* @return \Drupal\Core\Access\AccessResultInterface
* The access result.
*/
protected function checkEntityOwnerPermissions(EntityInterface $entity, $operation, AccountInterface $account) {
/** @var \Drupal\Core\Entity\EntityInterface|\Drupal\user\EntityOwnerInterface $entity */
if (($account->id() == $entity->getOwnerId())) {
if ($operation === 'view' && $entity instanceof EntityPublishedInterface && !$entity->isPublished()) {
$permissions = [
"view own unpublished {$entity->getEntityTypeId()}",
];
}
else {
$permissions = [
"$operation own {$entity->getEntityTypeId()}",
"$operation any {$entity->getEntityTypeId()}",
"$operation own {$entity->bundle()} {$entity->getEntityTypeId()}",
"$operation any {$entity->bundle()} {$entity->getEntityTypeId()}",
];
}
$result = AccessResult::allowedIfHasPermissions($account, $permissions, 'OR');
}
else {
$result = AccessResult::allowedIfHasPermissions($account, [
"$operation any {$entity->getEntityTypeId()}",
"$operation any {$entity->bundle()} {$entity->getEntityTypeId()}",
], 'OR');
}
return $result->cachePerUser();
}
/**
* {@inheritdoc}
*/
protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) {
$result = parent::checkCreateAccess($account, $context, $entity_bundle);
if ($result->isNeutral()) {
$permissions = [
'administer ' . $this->entityTypeId,
'create ' . $this->entityTypeId,
];
if ($entity_bundle) {
$permissions[] = 'create ' . $entity_bundle . ' ' . $this->entityTypeId;
}
$result = AccessResult::allowedIfHasPermissions($account, $permissions, 'OR');
}
return $result;
}
}
@@ -0,0 +1,143 @@
<?php
namespace Drupal\entity;
use Drupal\Core\Entity\EntityHandlerInterface;
use Drupal\Core\Entity\EntityPublishedInterface;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\user\EntityOwnerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides generic entity permissions which are cached per user.
*
* This includes:
*
* - administer $entity_type
* - access $entity_type overview
* - view an ($bundle) $entity_type
* - view own ($bundle) $entity_type
* - view own unpublished $entity_type
* - update (own|any) ($bundle) $entity_type
* - delete (own|any) ($bundle) $entity_type
* - create $bundle $entity_type
*
* As this class supports "view own ($bundle) $entity_type" it is just cacheable
* per user, which might harm performance of sites. Given that please use
* \Drupal\entity\EntityPermissionProvider unless you need the feature, or your
* entity type is not really user facing (commerce orders for example).
*
* Intended for content entity types, since config entity types usually rely
* on a single "administer" permission.
* Example annotation:
* @code
* handlers = {
* "access" = "Drupal\entity\UncacheableEntityAccessControlHandler",
* "permission_provider" = "Drupal\entity\UncacheableEntityPermissionProvider",
* }
* @endcode
*
* @see \Drupal\entity\EntityAccessControlHandler
* @see \Drupal\entity\EntityPermissions
*/
class UncacheableEntityPermissionProvider extends EntityPermissionProviderBase {
/**
* Builds permissions for the entity_type granularity.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type.
*
* @return array
* The permissions.
*/
protected function buildEntityTypePermissions(EntityTypeInterface $entity_type) {
$permissions = parent::buildEntityTypePermissions($entity_type);
$entity_type_id = $entity_type->id();
$has_owner = $entity_type->entityClassImplements(EntityOwnerInterface::class);
$plural_label = $entity_type->getPluralLabel();
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 any {$entity_type_id}"] = [
'title' => $this->t('View any @type', [
'@type' => $plural_label,
]),
];
}
return $permissions;
}
/**
* Builds permissions for the bundle granularity.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type.
*
* @return array
* The permissions.
*/
protected function buildBundlePermissions(EntityTypeInterface $entity_type) {
$permissions = parent::buildBundlePermissions($entity_type);
$entity_type_id = $entity_type->id();
$bundles = $this->entityTypeBundleInfo->getBundleInfo($entity_type_id);
$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 own {$entity_type_id}"] = [
'title' => $this->t('View own @type', [
'@type' => $plural_label,
]),
];
}
foreach ($bundles as $bundle_name => $bundle_info) {
if ($has_owner) {
$permissions["view any {$bundle_name} {$entity_type_id}"] = [
'title' => $this->t('@bundle: View any @type', [
'@bundle' => $bundle_info['label'],
'@type' => $plural_label,
]),
];
$permissions["view own {$bundle_name} {$entity_type_id}"] = [
'title' => $this->t('@bundle: View own @type', [
'@bundle' => $bundle_info['label'],
'@type' => $plural_label,
]),
];
}
else {
$permissions["view any {$bundle_name} {$entity_type_id}"] = [
'title' => $this->t('@bundle: View any @type', [
'@bundle' => $bundle_info['label'],
'@type' => $plural_label,
]),
];
}
}
return $permissions;
}
}