updated contrib modules : pathauto, token, entity_api, search_api, redirect, features

This commit is contained in:
2018-09-12 15:05:44 +02:00
parent b8e8cc8cee
commit a4746432c7
281 changed files with 4839 additions and 2453 deletions
@@ -5,8 +5,8 @@ type: module
dependencies:
- drupal:system (>=8.5.0)
# Information added by Drupal.org packaging script on 2018-03-12
version: '8.x-1.0-beta2'
# Information added by Drupal.org packaging script on 2018-06-08
version: '8.x-1.0-beta4'
core: '8.x'
project: 'entity'
datestamp: 1520873296
datestamp: 1528452194
@@ -69,6 +69,20 @@ class EntityRevisionRouteAccessChecker implements AccessInterface {
}
}
/**
* Performs access checks.
*
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
* The entity for which to check access.
* @param \Drupal\Core\Session\AccountInterface $account
* The user for which to check access.
* @param string $operation
* The entity operation. Usually one of 'view', 'view label', 'update' or
* 'delete'.
*
* @return bool
* The access result.
*/
protected function checkAccess(ContentEntityInterface $entity, AccountInterface $account, $operation = 'view') {
$entity_type = $entity->getEntityType();
$entity_type_id = $entity->getEntityTypeId();
@@ -126,7 +140,6 @@ class EntityRevisionRouteAccessChecker implements AccessInterface {
return $this->accessCache[$cid];
}
/**
* Counts the number of revisions in the default language.
*
@@ -0,0 +1,49 @@
<?php
namespace Drupal\entity;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\EntityAccessControlHandler as CoreEntityAccessControlHandler;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Session\AccountInterface;
/**
* Controls access to bundle entities.
*
* Allows the bundle entity label to be viewed if the account has
* access to view entities of that bundle.
*/
class BundleEntityAccessControlHandler extends CoreEntityAccessControlHandler {
/**
* {@inheritdoc}
*/
protected $viewLabelOperation = TRUE;
/**
* {@inheritdoc}
*/
protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) {
if ($operation === 'view label') {
$bundle = $entity->id();
$entity_type_id = $this->entityType->getBundleOf();
$permissions = [
"administer $entity_type_id",
// View permissions provided by EntityPermissionProvider.
"view $entity_type_id",
"view $bundle $entity_type_id",
// View permissions provided by UncacheableEntityPermissionProvider.
"view own $entity_type_id",
"view any $entity_type_id",
"view own $bundle $entity_type_id",
"view any $bundle $entity_type_id",
];
return AccessResult::allowedIfHasPermissions($account, $permissions, 'OR');
}
else {
return parent::checkAccess($entity, $operation, $account);
}
}
}
@@ -60,7 +60,7 @@ class BundlePluginUninstallValidator implements ModuleUninstallValidatorInterfac
});
$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'],
@@ -0,0 +1,14 @@
<?php
namespace Drupal\entity\Entity;
use Drupal\Core\Entity\RevisionableEntityBundleInterface as CoreRevisionableEntityBundleInterface;
@trigger_error('\Drupal\entity\Entity\RevisionableEntityBundleInterface has been deprecated in favor of \Drupal\Core\Entity\RevisionableEntityBundleInterface. Use that instead.');
/**
* @deprecated in favor of
* \Drupal\Core\Entity\RevisionableEntityBundleInterface. Use that instead.
*/
interface RevisionableEntityBundleInterface extends CoreRevisionableEntityBundleInterface {
}
@@ -3,19 +3,17 @@
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\UncacheableEntityPermissionProvider
*/
class EntityAccessControlHandler extends CoreEntityAccessControlHandler {
class EntityAccessControlHandler extends EntityAccessControlHandlerBase {
/**
* {@inheritdoc}
@@ -24,126 +22,36 @@ class EntityAccessControlHandler extends CoreEntityAccessControlHandler {
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}");
throw new \Exception('\Drupal\entity\EntityAccessControlHandler requires the \Drupal\entity\EntityPermissionProvider permission provider.');
}
}
/**
* {@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) {
if ($operation === 'view') {
$permissions = [
"view {$entity->getEntityTypeId()}"
];
}
else {
$permissions = [
"$operation {$entity->getEntityTypeId()}",
"$operation {$entity->bundle()} {$entity->getEntityTypeId()}",
];
}
return AccessResult::allowedIfHasPermissions($account, $permissions, '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\user\EntityOwnerInterface $entity */
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();
if ($account->id() != $entity->getOwnerId()) {
// There's no permission for viewing other user's unpublished entity.
return AccessResult::neutral()->cachePerUser();
}
return AccessResult::neutral()->cachePerUser();
$permissions = [
"view own unpublished {$entity->getEntityTypeId()}",
];
$result = AccessResult::allowedIfHasPermissions($account, $permissions)->cachePerUser();
}
else {
return AccessResult::allowedIfHasPermissions($account, [
$result = AccessResult::allowedIfHasPermissions($account, [
"view {$entity->getEntityTypeId()}",
]);
"view {$entity->bundle()} {$entity->getEntityTypeId()}",
], 'OR');
}
}
else {
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;
}
}
/**
* {@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');
$result = parent::checkEntityOwnerPermissions($entity, $operation, $account);
}
return $result;
@@ -0,0 +1,115 @@
<?php
namespace Drupal\entity;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\EntityAccessControlHandler as CoreEntityAccessControlHandler;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\user\EntityOwnerInterface;
/**
* @internal
*/
class EntityAccessControlHandlerBase extends CoreEntityAccessControlHandler {
/**
* {@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) {
$permissions = [
"$operation {$entity->getEntityTypeId()}",
"$operation {$entity->bundle()} {$entity->getEntityTypeId()}",
];
return AccessResult::allowedIfHasPermissions($account, $permissions, '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\user\EntityOwnerInterface $entity */
if ($account->id() == $entity->getOwnerId()) {
$permissions = [
"$operation own {$entity->getEntityTypeId()}",
"$operation any {$entity->getEntityTypeId()}",
"$operation own {$entity->bundle()} {$entity->getEntityTypeId()}",
"$operation any {$entity->bundle()} {$entity->getEntityTypeId()}",
];
}
else {
$permissions = [
"$operation any {$entity->getEntityTypeId()}",
"$operation any {$entity->bundle()} {$entity->getEntityTypeId()}",
];
}
$result = AccessResult::allowedIfHasPermissions($account, $permissions, 'OR')->cachePerUser();
return $result;
}
/**
* {@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;
}
}
@@ -5,24 +5,25 @@ namespace Drupal\entity;
use Drupal\Core\Entity\EntityTypeInterface;
/**
* Provides generic entity permissions which are still cacheable.
* Provides generic entity permissions.
*
* This includes:
* Intended for content entity types, since config entity types usually rely
* on a single "administer" permission.
*
* Provided permissions:
* - administer $entity_type
* - access $entity_type overview
* - view $entity_type
* - view ($bundle) $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.
* Does not provide "view own ($bundle) $entity_type" permissions, because
* they require caching pages per user. Please use
* \Drupal\entity\UncacheableEntityPermissionProvider if those permissions
* are necessary.
*
* Intended for content entity types, since config entity types usually rely
* on a single "administer" permission.
* Example annotation:
* @code
* handlers = {
@@ -37,23 +38,58 @@ use Drupal\Core\Entity\EntityTypeInterface;
class EntityPermissionProvider extends EntityPermissionProviderBase {
/**
* {@inheritdoc}
* Builds permissions for the entity_type granularity.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type.
*
* @return array
* The permissions.
*/
public function buildPermissions(EntityTypeInterface $entity_type) {
protected function buildEntityTypePermissions(EntityTypeInterface $entity_type) {
$permissions = parent::buildEntityTypePermissions($entity_type);
$entity_type_id = $entity_type->id();
$plural_label = $entity_type->getPluralLabel();
$permissions = parent::buildPermissions($entity_type);
// View permissions are the same for both granularities.
$permissions["view {$entity_type_id}"] = [
'title' => $this->t('View @type', [
'@type' => $plural_label,
]),
];
return $this->processPermissions($permissions, $entity_type);
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);
$plural_label = $entity_type->getPluralLabel();
$permissions["view {$entity_type_id}"] = [
'title' => $this->t('View @type', [
'@type' => $plural_label,
]),
];
foreach ($bundles as $bundle_name => $bundle_info) {
$permissions["view {$bundle_name} {$entity_type_id}"] = [
'title' => $this->t('@bundle: View @type', [
'@bundle' => $bundle_info['label'],
'@type' => $plural_label,
]),
];
}
return $permissions;
}
}
@@ -56,9 +56,11 @@ class EntityPermissionProviderBase implements EntityPermissionProviderInterface,
'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 ($entity_type->hasLinkTemplate('collection')) {
$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', [
@@ -82,7 +84,7 @@ class EntityPermissionProviderBase implements EntityPermissionProviderInterface,
* Adds the provider and converts the titles to strings to allow sorting.
*
* @param array $permissions
* The array of permissions
* The array of permissions.
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type.
*
@@ -0,0 +1,17 @@
<?php
namespace Drupal\entity;
use Drupal\Core\Entity\EntityViewBuilder as CoreEntityViewBuilder;
@trigger_error('\Drupal\entity\EntityViewBuilder has been deprecated in favor of \Drupal\Core\Entity\EntityViewBuilder. Use that instead.');
/**
* Provides a entity view builder with contextual links support.
*
* @deprecated in favor of \Drupal\Core\Entity\EntityViewBuilder. Use that
* instead.
*/
class EntityViewBuilder extends CoreEntityViewBuilder {
}
@@ -0,0 +1,165 @@
<?php
namespace Drupal\entity\Form;
use Drupal\Core\Entity\ContentEntityForm;
use Drupal\Core\Entity\RevisionableEntityBundleInterface;
use Drupal\Core\Form\FormStateInterface;
@trigger_error('\Drupal\entity\Form\RevisionableContentEntityForm has been deprecated in favor of \Drupal\Core\Entity\ContentEntityForm. Use that instead.');
/**
* Extends the base entity form with revision support in the UI.
*
* @deprecated in favor of \Drupal\Core\Entity\ContentEntityForm. Use that
* 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
*/
protected $entity;
/**
* {@inheritdoc}
*/
protected function prepareEntity() {
parent::prepareEntity();
$bundle_entity = $this->getBundleEntity();
// Set up default values, if required.
if (!$this->entity->isNew()) {
$this->entity->setRevisionLogMessage(NULL);
}
if ($bundle_entity instanceof RevisionableEntityBundleInterface) {
// Always use the default revision setting.
$this->entity->setNewRevision($bundle_entity && $bundle_entity->shouldCreateNewRevision());
}
}
/**
* Gets the bundle entity of the current entity.
*
* @return \Drupal\Core\Entity\EntityInterface|null
* The bundle entity, or NULL if there is none.
*/
protected function getBundleEntity() {
if ($bundle_key = $this->entity->getEntityType()->getKey('bundle')) {
return $this->entity->{$bundle_key}->referencedEntities()[0];
}
return NULL;
}
/**
* {@inheritdoc}
*/
public function form(array $form, FormStateInterface $form_state) {
$entity_type = $this->entity->getEntityType();
$bundle_entity = $this->getBundleEntity();
$account = $this->currentUser();
if ($this->operation == 'edit') {
$form['#title'] = $this->t('Edit %bundle_label @label', [
'%bundle_label' => $bundle_entity ? $bundle_entity->label() : '',
'@label' => $this->entity->label(),
]);
}
$form['advanced'] = [
'#type' => 'vertical_tabs',
'#weight' => 99,
];
// Add a log field if the "Create new revision" option is checked, or if the
// current user has the ability to check that option.
// @todo Could we autogenerate this form by using some widget on the
// revision info field.
$form['revision_information'] = [
'#type' => 'details',
'#title' => $this->t('Revision information'),
// Open by default when "Create new revision" is checked.
'#open' => $this->entity->isNewRevision(),
'#group' => 'advanced',
'#weight' => 20,
'#access' => $this->entity->isNewRevision() || $account->hasPermission($entity_type->get('admin_permission')),
];
$form['revision_information']['revision'] = [
'#type' => 'checkbox',
'#title' => $this->t('Create new revision'),
'#default_value' => $this->entity->isNewRevision(),
'#access' => $account->hasPermission($entity_type->get('admin_permission')),
];
// Check the revision log checkbox when the log textarea is filled in.
// This must not happen if "Create new revision" is enabled by default,
// since the state would auto-disable the checkbox otherwise.
if (!$this->entity->isNewRevision()) {
$form['revision_information']['revision']['#states'] = [
'checked' => [
'textarea[name="revision_log"]' => ['empty' => FALSE],
],
];
}
$form['revision_information']['revision_log'] = [
'#type' => 'textarea',
'#title' => $this->t('Revision log message'),
'#rows' => 4,
'#default_value' => $this->entity->getRevisionLogMessage(),
'#description' => $this->t('Briefly describe the changes you have made.'),
];
return parent::form($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function save(array $form, FormStateInterface $form_state) {
// Save as a new revision if requested to do so.
if (!$form_state->isValueEmpty('revision')) {
$this->entity->setNewRevision();
}
$insert = $this->entity->isNew();
$this->entity->save();
$context = ['@type' => $this->entity->bundle(), '%info' => $this->entity->label()];
$logger = $this->logger('content');
$bundle_entity = $this->getBundleEntity();
$t_args = ['@type' => $bundle_entity ? $bundle_entity->label() : 'None', '%info' => $this->entity->label()];
if ($insert) {
$logger->notice('@type: added %info.', $context);
drupal_set_message($this->t('@type %info has been created.', $t_args));
}
else {
$logger->notice('@type: updated %info.', $context);
drupal_set_message($this->t('@type %info has been updated.', $t_args));
}
if ($this->entity->id()) {
$form_state->setValue('id', $this->entity->id());
$form_state->set('id', $this->entity->id());
if ($this->entity->getEntityType()->hasLinkTemplate('collection')) {
$form_state->setRedirectUrl($this->entity->toUrl('collection'));
}
else {
$form_state->setRedirectUrl($this->entity->toUrl('canonical'));
}
}
else {
// In the unlikely case something went wrong on save, the entity will be
// rebuilt and entity form redisplayed.
drupal_set_message($this->t('The entity could not be saved.'), 'error');
$form_state->setRebuild();
}
}
}
@@ -44,7 +44,7 @@ class DeleteAction extends ActionBase implements ContainerFactoryPluginInterface
* The plugin implementation definition.
* @param \Drupal\Core\TempStore\PrivateTempStoreFactory $temp_store_factory
* The tempstore factory.
* @param AccountInterface $current_user
* @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) {
@@ -3,12 +3,10 @@
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.
@@ -17,7 +15,7 @@ use Drupal\user\EntityOwnerInterface;
*
* Note: this access control handler will cause pages to be cached per user.
*/
class UncacheableEntityAccessControlHandler extends CoreEntityAccessControlHandler {
class UncacheableEntityAccessControlHandler extends EntityAccessControlHandlerBase {
/**
* {@inheritdoc}
@@ -26,110 +24,28 @@ class UncacheableEntityAccessControlHandler extends CoreEntityAccessControlHandl
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}");
throw new \Exception('\Drupal\entity\UncacheableEntityAccessControlHandler requires the \Drupal\entity\UncacheableEntityPermissionProvider permission provider.');
}
}
/**
* {@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()}",
];
/** @var \Drupal\user\EntityOwnerInterface $entity */
if ($operation === 'view' && $entity instanceof EntityPublishedInterface && !$entity->isPublished()) {
if ($account->id() != $entity->getOwnerId()) {
// There's no permission for viewing other user's unpublished entity.
return AccessResult::neutral()->cachePerUser();
}
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');
$permissions = [
"view own unpublished {$entity->getEntityTypeId()}",
];
$result = AccessResult::allowedIfHasPermissions($account, $permissions)->cachePerUser();
}
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');
$result = parent::checkEntityOwnerPermissions($entity, $operation, $account);
}
return $result;
@@ -8,24 +8,25 @@ use Drupal\user\EntityOwnerInterface;
/**
* Provides generic entity permissions which are cached per user.
*
* This includes:
* Intended for content entity types, since config entity types usually rely
* on a single "administer" permission.
*
* Provided permissions:
* - administer $entity_type
* - access $entity_type overview
* - view an ($bundle) $entity_type
* - view any ($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).
* Important:
* Provides "view own ($bundle) $entity_type" permissions, which require
* caching pages per user. This can significantly increase the size of caches,
* impacting site performance. Use \Drupal\entity\EntityPermissionProvider
* if those permissions are not necessary.
*
* Intended for content entity types, since config entity types usually rely
* on a single "administer" permission.
* Example annotation:
* @code
* handlers = {
@@ -6,8 +6,8 @@ package: Testing
dependencies:
- entity
# Information added by Drupal.org packaging script on 2018-03-12
version: '8.x-1.0-beta2'
# Information added by Drupal.org packaging script on 2018-06-08
version: '8.x-1.0-beta4'
core: '8.x'
project: 'entity'
datestamp: 1520873296
datestamp: 1528452194
@@ -6,8 +6,8 @@ package: Testing
dependencies:
- entity
# Information added by Drupal.org packaging script on 2018-03-12
version: '8.x-1.0-beta2'
# Information added by Drupal.org packaging script on 2018-06-08
version: '8.x-1.0-beta4'
core: '8.x'
project: 'entity'
datestamp: 1520873296
datestamp: 1528452194
@@ -3,8 +3,8 @@ type: module
package: Testing
# core: 8.x
# Information added by Drupal.org packaging script on 2018-03-12
version: '8.x-1.0-beta2'
# Information added by Drupal.org packaging script on 2018-06-08
version: '8.x-1.0-beta4'
core: '8.x'
project: 'entity'
datestamp: 1520873296
datestamp: 1528452194
@@ -24,8 +24,8 @@ use Drupal\entity\Revision\RevisionableContentEntityBase;
* "access" = "\Drupal\Core\Entity\EntityAccessControlHandler",
* "permission_provider" = "\Drupal\entity\EntityPermissionProvider",
* "form" = {
* "add" = "\Drupal\Core\Entity\ContentEntityForm",
* "edit" = "\Drupal\Core\Entity\ContentEntityForm",
* "add" = "\Drupal\entity\Form\RevisionableContentEntityForm",
* "edit" = "\Drupal\entity\Form\RevisionableContentEntityForm",
* "delete" = "\Drupal\Core\Entity\EntityDeleteForm",
* },
* "route_provider" = {
@@ -15,6 +15,9 @@ use Drupal\Core\Entity\RevisionableEntityBundleInterface;
* admin_permission = "administer entity_test_enhanced",
* config_prefix = "entity_test_enhanced_bundle",
* bundle_of = "entity_test_enhanced",
* handlers = {
* "access" = "\Drupal\entity\BundleEntityAccessControlHandler",
* },
* entity_keys = {
* "id" = "id",
* "label" = "label"
@@ -23,7 +23,7 @@ class RevisionRouteAccessTest extends BrowserTestBase {
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface;
* @var \Drupal\Core\Session\AccountInterface
*/
protected $account;
@@ -0,0 +1,56 @@
<?php
namespace Drupal\Tests\entity\Kernel;
use Drupal\entity_module_test\Entity\EnhancedEntityBundle;
use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
/**
* Tests the bundle entity access control handler.
*
* @group entity
*/
class BundleEntityAccessControlHandlerTest extends EntityKernelTestBase {
/**
* {@inheritdoc}
*/
public static $modules = [
'entity',
'entity_module_test',
];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installEntitySchema('entity_test_enhanced');
}
/**
* Tests the "view label" access checking.
*/
public function testAccess() {
$bundle = EnhancedEntityBundle::create([
'id' => 'default',
'label' => 'Default',
]);
$bundle->save();
// The default user has no permissions related to entity_test_enhanced.
$this->assertFalse($bundle->access('view label'));
$permissions = [
'administer entity_test_enhanced',
'view entity_test_enhanced',
'view default entity_test_enhanced',
];
foreach ($permissions as $permission) {
$account = $this->createUser([], [$permission]);
$this->assertTrue($bundle->access('view label', $account));
}
}
}
@@ -11,6 +11,7 @@ use Drupal\KernelTests\KernelTestBase;
/**
* Tests the delete entity action.
*
* @group entity
*/
class DeleteActionTest extends KernelTestBase {
@@ -25,8 +26,9 @@ class DeleteActionTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['action', 'node', 'entity_module_test', 'entity',
'user', 'system'];
public static $modules = [
'action', 'node', 'entity_module_test', 'entity', 'user', 'system',
];
/**
* {@inheritdoc}
@@ -10,6 +10,8 @@ use Symfony\Component\Routing\Route;
*
* - Are the routes added properly.
* - Are the local tasks added properly.
*
* @group entity
*/
class RevisionOverviewIntegrationTest extends KernelTestBase {
@@ -77,84 +77,71 @@ class EntityAccessControlHandlerTest extends UnitTestCase {
* A list of testAccess method arguments.
*/
public function accessProvider() {
$data = [];
$entity_type = $this->prophesize(ContentEntityTypeInterface::class);
$entity_type->id()->willReturn('green_entity');
$entity_type->getAdminPermission()->willReturn('administer green_entity');
$entity_type->hasHandlerClass('permission_provider')->willReturn(TRUE);
$entity_type->getHandlerClass('permission_provider')->willReturn(EntityPermissionProvider::class);
// User with the admin permission can do anything.
$entity = $this->buildMockEntity($entity_type->reveal());
$account = $this->prophesize(AccountInterface::class);
$account->id()->willReturn(6);
$account->hasPermission('administer green_entity')->willReturn(TRUE);
$data[] = [$entity->reveal(), 'view', $account->reveal(), TRUE];
$data[] = [$entity->reveal(), 'update', $account->reveal(), TRUE];
$data[] = [$entity->reveal(), 'delete', $account->reveal(), TRUE];
// Entity with no owner.
$entity = $this->buildMockEntity($entity_type->reveal());
// User who has access.
$first_account = $this->prophesize(AccountInterface::class);
$first_account->id()->willReturn(6);
$first_account->hasPermission('view green_entity')->willReturn(TRUE);
$first_account->hasPermission(Argument::any())->willReturn(FALSE);
// User who doesn't have access.
$second_account = $this->prophesize(AccountInterface::class);
$second_account->id()->willReturn(7);
$second_account->hasPermission('view green_entity')->willReturn(FALSE);
$second_account->hasPermission(Argument::any())->willReturn(FALSE);
$data[] = [$entity->reveal(), 'view', $first_account->reveal(), TRUE];
$data[] = [$entity->reveal(), 'view', $second_account->reveal(), FALSE];
// Entity with owner.
$entity = $this->buildMockEntity($entity_type->reveal(), 6);
// Owner.
$first_account = $this->prophesize(AccountInterface::class);
$first_account->id()->willReturn(6);
$first_account->hasPermission('update own green_entity')->willReturn(TRUE);
$first_account->hasPermission(Argument::any())->willReturn(FALSE);
// Non-owner.
$second_account = $this->prophesize(AccountInterface::class);
$second_account->id()->willReturn(7);
$second_account->hasPermission('update own green_entity')->willReturn(TRUE);
$second_account->hasPermission(Argument::any())->willReturn(FALSE);
// User who can update any.
$third_account = $this->prophesize(AccountInterface::class);
$third_account->id()->willReturn(8);
$third_account->hasPermission('update any green_entity')->willReturn(TRUE);
$third_account->hasPermission(Argument::any())->willReturn(FALSE);
$data[] = [$entity->reveal(), 'update', $first_account->reveal(), TRUE];
$data[] = [$entity->reveal(), 'update', $second_account->reveal(), FALSE];
$data[] = [$entity->reveal(), 'update', $third_account->reveal(), TRUE];
// Test the unpublished permissions.
$entity_first_other_up = $this->buildMockEntity($entity_type->reveal(), 9999, 'first', FALSE);
$entity_first_own_up = $this->buildMockEntity($entity_type->reveal(), 14, 'first', FALSE);
$entity_first_own_bundle_up = $this->buildMockEntity($entity_type->reveal(), 15, 'first', FALSE);
$data = [];
// Admin permission.
$admin_user = $this->buildMockUser(5, 'administer green_entity');
$data[] = [$entity->reveal(), 'view', $admin_user->reveal(), TRUE];
$data[] = [$entity->reveal(), 'update', $admin_user->reveal(), TRUE];
$data[] = [$entity->reveal(), 'delete', $admin_user->reveal(), TRUE];
$entity_second_other_up = $this->buildMockEntity($entity_type->reveal(), 9999, 'second', FALSE);
$entity_second_own_up = $this->buildMockEntity($entity_type->reveal(), 14, 'second', FALSE);
$entity_second_own_bundle_up = $this->buildMockEntity($entity_type->reveal(), 15, 'second', FALSE);
// View, Update, delete permissions, entity without an owner.
$second_entity = $this->buildMockEntity($entity_type->reveal());
foreach (['view', 'update', 'delete'] as $operation) {
$first_user = $this->buildMockUser(6, $operation . ' green_entity');
$second_user = $this->buildMockUser(7, 'access content');
$user_view_own_up = $this->buildMockUser(14, 'view own unpublished green_entity');
$user_view_other = $this->buildMockUser(15, 'view green_entity');
$data[] = [$second_entity->reveal(), $operation, $first_user->reveal(), TRUE];
$data[] = [$second_entity->reveal(), $operation, $second_user->reveal(), FALSE];
}
$data['entity_first_other_up user_view_own_up'] = [$entity_first_other_up->reveal(), 'view', $user_view_own_up->reveal(), FALSE];
$data['entity_first_own_up user_view_own_up'] = [$entity_first_own_up->reveal(), 'view', $user_view_own_up->reveal(), TRUE];
$data['entity_first_own_bundle_up user_view_own_up'] = [$entity_first_own_bundle_up->reveal(), 'view', $user_view_own_up->reveal(), FALSE];
$data['entity_second_other_up user_view_own_up'] = [$entity_second_other_up->reveal(), 'view', $user_view_own_up->reveal(), FALSE];
$data['entity_second_own_up user_view_own_up'] = [$entity_second_own_up->reveal(), 'view', $user_view_own_up->reveal(), TRUE];
$data['entity_second_own_bundle_up user_view_own_up'] = [$entity_second_own_bundle_up->reveal(), 'view', $user_view_own_up->reveal(), FALSE];
// Update and delete permissions.
foreach (['update', 'delete'] as $operation) {
// Owner, non-owner, user with "any" permission.
$first_user = $this->buildMockUser(6, $operation . ' own green_entity');
$second_user = $this->buildMockUser(7, $operation . ' own green_entity');
$third_user = $this->buildMockUser(8, $operation . ' any green_entity');
$data['entity_first_other_up user_view_other'] = [$entity_first_other_up->reveal(), 'view', $user_view_other->reveal(), FALSE];
$data['entity_first_own_up user_view_other'] = [$entity_first_own_up->reveal(), 'view', $user_view_other->reveal(), FALSE];
$data['entity_first_own_bundle_up user_view_other'] = [$entity_first_own_bundle_up->reveal(), 'view', $user_view_other->reveal(), FALSE];
$data['entity_second_other_up user_view_other'] = [$entity_second_other_up->reveal(), 'view', $user_view_other->reveal(), FALSE];
$data['entity_second_own_up user_view_other'] = [$entity_second_own_up->reveal(), 'view', $user_view_other->reveal(), FALSE];
$data['entity_second_own_bundle_up user_view_other'] = [$entity_second_own_bundle_up->reveal(), 'view', $user_view_other->reveal(), FALSE];
$data[] = [$entity->reveal(), $operation, $first_user->reveal(), TRUE];
$data[] = [$entity->reveal(), $operation, $second_user->reveal(), FALSE];
$data[] = [$entity->reveal(), $operation, $third_user->reveal(), TRUE];
}
// View permissions.
$first_user = $this->buildMockUser(9, 'view green_entity');
$second_user = $this->buildMockUser(10, 'view first green_entity');
$third_user = $this->buildMockUser(14, 'view own unpublished green_entity');
$fourth_user = $this->buildMockUser(14, 'access content');
$first_entity = $this->buildMockEntity($entity_type->reveal(), 1, 'first');
$second_entity = $this->buildMockEntity($entity_type->reveal(), 1, 'second');
$third_entity = $this->buildMockEntity($entity_type->reveal(), 14, 'first', FALSE);
// The first user can view the two published entities.
$data[] = [$first_entity->reveal(), 'view', $first_user->reveal(), TRUE];
$data[] = [$second_entity->reveal(), 'view', $first_user->reveal(), TRUE];
$data[] = [$third_entity->reveal(), 'view', $first_user->reveal(), FALSE];
// The second user can only view published entities of bundle "first".
$data[] = [$first_entity->reveal(), 'view', $second_user->reveal(), TRUE];
$data[] = [$second_entity->reveal(), 'view', $second_user->reveal(), FALSE];
$data[] = [$third_entity->reveal(), 'view', $second_user->reveal(), FALSE];
// The third user can view their own unpublished entity.
$data[] = [$first_entity->reveal(), 'view', $third_user->reveal(), FALSE];
$data[] = [$second_entity->reveal(), 'view', $third_user->reveal(), FALSE];
$data[] = [$third_entity->reveal(), 'view', $third_user->reveal(), TRUE];
// The fourth user can't view anything.
$data[] = [$first_entity->reveal(), 'view', $fourth_user->reveal(), FALSE];
$data[] = [$second_entity->reveal(), 'view', $fourth_user->reveal(), FALSE];
$data[] = [$third_entity->reveal(), 'view', $fourth_user->reveal(), FALSE];
return $data;
}
@@ -175,29 +162,19 @@ class EntityAccessControlHandlerTest extends UnitTestCase {
$entity_type->getHandlerClass('permission_provider')->willReturn(EntityPermissionProvider::class);
// User with the admin permission.
$account = $this->prophesize(AccountInterface::class);
$account->id()->willReturn(6);
$account->hasPermission('administer green_entity')->willReturn(TRUE);
$account = $this->buildMockUser('6', 'administer green_entity');
$data[] = [$entity_type->reveal(), NULL, $account->reveal(), TRUE];
// Ordinary user.
$account = $this->prophesize(AccountInterface::class);
$account->id()->willReturn(6);
$account->hasPermission('create green_entity')->willReturn(TRUE);
$account->hasPermission(Argument::any())->willReturn(FALSE);
$account = $this->buildMockUser('6', 'create green_entity');
$data[] = [$entity_type->reveal(), NULL, $account->reveal(), TRUE];
// Ordinary user, entity with a bundle.
$account = $this->prophesize(AccountInterface::class);
$account->id()->willReturn(6);
$account->hasPermission('create first_bundle green_entity')->willReturn(TRUE);
$account->hasPermission(Argument::any())->willReturn(FALSE);
$account = $this->buildMockUser('6', 'create first_bundle green_entity');
$data[] = [$entity_type->reveal(), 'first_bundle', $account->reveal(), TRUE];
// User with no permissions.
$account = $this->prophesize(AccountInterface::class);
$account->id()->willReturn(6);
$account->hasPermission(Argument::any())->willReturn(FALSE);
$account = $this->buildMockUser('6', 'access content');
$data[] = [$entity_type->reveal(), NULL, $account->reveal(), FALSE];
return $data;
@@ -210,6 +187,10 @@ class EntityAccessControlHandlerTest extends UnitTestCase {
* The entity type.
* @param string $owner_id
* The owner ID.
* @param string $bundle
* The bundle.
* @param bool $published
* Whether the entity is published.
*
* @return \Prophecy\Prophecy\ObjectProphecy
* The entity mock.
@@ -242,15 +223,26 @@ class EntityAccessControlHandlerTest extends UnitTestCase {
$entity->getCacheTags()->willReturn([]);
$entity->getCacheMaxAge()->willReturn(Cache::PERMANENT);
return $entity;
}
/**
* Builds a mock user.
*
* @param int $uid
* The user ID.
* @param string $permission
* The permission to grant.
*
* @return \Prophecy\Prophecy\ObjectProphecy
* The user mock.
*/
protected function buildMockUser($uid, $permission) {
$account = $this->prophesize(AccountInterface::class);
$account->id()->willReturn($uid);
$account->hasPermission($permission)->willReturn(TRUE);
$account->hasPermission(Argument::any())->willReturn(FALSE);
return $account;
}
@@ -72,12 +72,12 @@ class EntityPermissionProviderTest extends UnitTestCase {
$entity_type->id()->willReturn('green_entity');
$entity_type->getSingularLabel()->willReturn('green entity');
$entity_type->getPluralLabel()->willReturn('green entities');
$entity_type->hasLinkTemplate('collection')->willReturn(FALSE);
$entity_type->entityClassImplements(EntityOwnerInterface::class)->willReturn(FALSE);
$entity_type->entityClassImplements(EntityPublishedInterface::class)->willReturn(FALSE);
$entity_type->getPermissionGranularity()->willReturn('entity_type');
$expected_permissions = [
'administer green_entity' => 'Administer green entities',
'access green_entity overview' => 'Access the green entities overview page',
'create green_entity' => 'Create green entities',
'update green_entity' => 'Update green entities',
'delete green_entity' => 'Delete green entities',
@@ -91,6 +91,7 @@ class EntityPermissionProviderTest extends UnitTestCase {
$entity_type->id()->willReturn('blue_entity');
$entity_type->getSingularLabel()->willReturn('blue entity');
$entity_type->getPluralLabel()->willReturn('blue entities');
$entity_type->hasLinkTemplate('collection')->willReturn(TRUE);
$entity_type->entityClassImplements(EntityOwnerInterface::class)->willReturn(TRUE);
$entity_type->entityClassImplements(EntityPublishedInterface::class)->willReturn(FALSE);
$entity_type->getPermissionGranularity()->willReturn('entity_type');
@@ -112,6 +113,7 @@ class EntityPermissionProviderTest extends UnitTestCase {
$entity_type->id()->willReturn('white_entity');
$entity_type->getSingularLabel()->willReturn('white entity');
$entity_type->getPluralLabel()->willReturn('white entities');
$entity_type->hasLinkTemplate('collection')->willReturn(TRUE);
$entity_type->entityClassImplements(EntityOwnerInterface::class)->willReturn(FALSE);
$entity_type->entityClassImplements(EntityPublishedInterface::class)->willReturn(FALSE);
$entity_type->getPermissionGranularity()->willReturn('bundle');
@@ -125,6 +127,8 @@ class EntityPermissionProviderTest extends UnitTestCase {
'update second white_entity' => 'Second: Update white entities',
'delete second white_entity' => 'Second: Delete white entities',
'view white_entity' => 'View white entities',
'view first white_entity' => 'First: View white entities',
'view second white_entity' => 'Second: View white entities',
];
$data[] = [$entity_type->reveal(), $expected_permissions];
@@ -134,6 +138,7 @@ class EntityPermissionProviderTest extends UnitTestCase {
$entity_type->id()->willReturn('black_entity');
$entity_type->getSingularLabel()->willReturn('black entity');
$entity_type->getPluralLabel()->willReturn('black entities');
$entity_type->hasLinkTemplate('collection')->willReturn(TRUE);
$entity_type->entityClassImplements(EntityOwnerInterface::class)->willReturn(TRUE);
$entity_type->entityClassImplements(EntityPublishedInterface::class)->willReturn(FALSE);
$entity_type->getPermissionGranularity()->willReturn('bundle');
@@ -146,6 +151,7 @@ class EntityPermissionProviderTest extends UnitTestCase {
'delete any third black_entity' => 'Third: Delete any black entity',
'delete own third black_entity' => 'Third: Delete own black entities',
'view black_entity' => 'View black entities',
'view third black_entity' => 'Third: View black entities',
];
$data[] = [$entity_type->reveal(), $expected_permissions];
@@ -155,6 +161,7 @@ class EntityPermissionProviderTest extends UnitTestCase {
$entity_type->id()->willReturn('pink_entity');
$entity_type->getSingularLabel()->willReturn('pink entity');
$entity_type->getPluralLabel()->willReturn('pink entities');
$entity_type->hasLinkTemplate('collection')->willReturn(TRUE);
$entity_type->entityClassImplements(EntityOwnerInterface::class)->willReturn(TRUE);
$entity_type->entityClassImplements(EntityPublishedInterface::class)->willReturn(TRUE);
$entity_type->getPermissionGranularity()->willReturn('bundle');
@@ -168,6 +175,7 @@ class EntityPermissionProviderTest extends UnitTestCase {
'delete any third pink_entity' => 'Third: Delete any pink entity',
'delete own third pink_entity' => 'Third: Delete own pink entities',
'view pink_entity' => 'View pink entities',
'view third pink_entity' => 'Third: View pink entities',
];
$data[] = [$entity_type->reveal(), $expected_permissions];
@@ -21,7 +21,7 @@ use Drupal\user\EntityOwnerInterface;
use Prophecy\Argument;
/**
* @coversDefaultClass \Drupal\entity\EntityAccessControlHandler
* @coversDefaultClass \Drupal\entity\UncacheableEntityAccessControlHandler
* @group entity
*/
class UncacheableEntityAccessControlHandlerTest extends UnitTestCase {
@@ -77,126 +77,75 @@ class UncacheableEntityAccessControlHandlerTest extends UnitTestCase {
* A list of testAccess method arguments.
*/
public function accessProvider() {
$data = [];
$entity_type = $this->prophesize(ContentEntityTypeInterface::class);
$entity_type->id()->willReturn('green_entity');
$entity_type->getAdminPermission()->willReturn('administer green_entity');
$entity_type->hasHandlerClass('permission_provider')->willReturn(TRUE);
$entity_type->getHandlerClass('permission_provider')->willReturn(UncacheableEntityPermissionProvider::class);
// User with the admin permission can do anything.
$entity = $this->buildMockEntity($entity_type->reveal());
$account = $this->prophesize(AccountInterface::class);
$account->id()->willReturn(6);
$account->hasPermission('administer green_entity')->willReturn(TRUE);
$data[] = [$entity->reveal(), 'view', $account->reveal(), TRUE];
$data[] = [$entity->reveal(), 'update', $account->reveal(), TRUE];
$data[] = [$entity->reveal(), 'delete', $account->reveal(), TRUE];
// Entity with no owner.
$entity = $this->buildMockEntity($entity_type->reveal());
// User who has access.
$first_account = $this->prophesize(AccountInterface::class);
$first_account->id()->willReturn(6);
$first_account->hasPermission('view green_entity')->willReturn(TRUE);
$first_account->hasPermission(Argument::any())->willReturn(FALSE);
// User who doesn't have access.
$second_account = $this->prophesize(AccountInterface::class);
$second_account->id()->willReturn(7);
$second_account->hasPermission('view green_entity')->willReturn(FALSE);
$second_account->hasPermission(Argument::any())->willReturn(FALSE);
$data[] = [$entity->reveal(), 'view', $first_account->reveal(), TRUE];
$data[] = [$entity->reveal(), 'view', $second_account->reveal(), FALSE];
// Entity with owner.
$entity = $this->buildMockEntity($entity_type->reveal(), 6);
// Owner.
$first_account = $this->prophesize(AccountInterface::class);
$first_account->id()->willReturn(6);
$first_account->hasPermission('update own green_entity')->willReturn(TRUE);
$first_account->hasPermission(Argument::any())->willReturn(FALSE);
// Non-owner.
$second_account = $this->prophesize(AccountInterface::class);
$second_account->id()->willReturn(7);
$second_account->hasPermission('update own green_entity')->willReturn(TRUE);
$second_account->hasPermission(Argument::any())->willReturn(FALSE);
// User who can update any.
$third_account = $this->prophesize(AccountInterface::class);
$third_account->id()->willReturn(8);
$third_account->hasPermission('update any green_entity')->willReturn(TRUE);
$third_account->hasPermission(Argument::any())->willReturn(FALSE);
$data[] = [$entity->reveal(), 'update', $first_account->reveal(), TRUE];
$data[] = [$entity->reveal(), 'update', $second_account->reveal(), FALSE];
$data[] = [$entity->reveal(), 'update', $third_account->reveal(), TRUE];
// Per bundle permissions.
$entity_first_other = $this->buildMockEntity($entity_type->reveal(), 9999, 'first');
$entity_first_own = $this->buildMockEntity($entity_type->reveal(), 10, 'first');
$entity_first_own_bundle = $this->buildMockEntity($entity_type->reveal(), 12, 'first');
$data = [];
// Admin permission.
$admin_user = $this->buildMockUser(5, 'administer green_entity');
$data[] = [$entity->reveal(), 'view', $admin_user->reveal(), TRUE];
$data[] = [$entity->reveal(), 'update', $admin_user->reveal(), TRUE];
$data[] = [$entity->reveal(), 'delete', $admin_user->reveal(), TRUE];
$entity_second_other = $this->buildMockEntity($entity_type->reveal(), 9999, 'second');
$entity_second_own = $this->buildMockEntity($entity_type->reveal(), 10, 'second');
$entity_second_own_bundle = $this->buildMockEntity($entity_type->reveal(), 12, 'second');
$user_view_any = $this->buildMockUser(9, 'view any green_entity');
$user_view_own = $this->buildMockUser(10, 'view own green_entity');
$user_view_bundle_any = $this->buildMockUser(11, 'view any first green_entity');
$user_view_bundle_own = $this->buildMockUser(12, 'view own first green_entity');
// View, update, delete permissions, entity without an owner.
$second_entity = $this->buildMockEntity($entity_type->reveal());
foreach (['view', 'update', 'delete'] as $operation) {
$first_user = $this->buildMockUser(6, $operation . ' green_entity');
$second_user = $this->buildMockUser(7, 'access content');
$data['entity_first_other user_view_any'] = [$entity_first_other->reveal(), 'view', $user_view_any->reveal(), TRUE];
$data['entity_first_own user_view_any'] = [$entity_first_own->reveal(), 'view', $user_view_any->reveal(), TRUE];
$data['entity_first_own_bundle user_view_any'] = [$entity_first_own_bundle->reveal(), 'view', $user_view_any->reveal(), TRUE];
$data['entity_second_other user_view_any'] = [$entity_second_other->reveal(), 'view', $user_view_any->reveal(), TRUE];
$data['entity_second_own user_view_any'] = [$entity_second_own->reveal(), 'view', $user_view_any->reveal(), TRUE];
$data['entity_second_own_bundle user_view_any'] = [$entity_second_own_bundle->reveal(), 'view', $user_view_any->reveal(), TRUE];
$data[] = [$second_entity->reveal(), $operation, $first_user->reveal(), TRUE];
$data[] = [$second_entity->reveal(), $operation, $second_user->reveal(), FALSE];
}
$data['entity_first_other user_view_own'] = [$entity_first_other->reveal(), 'view', $user_view_own->reveal(), FALSE];
$data['entity_first_own user_view_own'] = [$entity_first_own->reveal(), 'view', $user_view_own->reveal(), TRUE];
$data['entity_first_own_bundle user_view_own'] = [$entity_first_own_bundle->reveal(), 'view', $user_view_own->reveal(), FALSE];
$data['entity_second_other user_view_own'] = [$entity_second_other->reveal(), 'view', $user_view_own->reveal(), FALSE];
$data['entity_second_own user_view_own'] = [$entity_second_own->reveal(), 'view', $user_view_own->reveal(), TRUE];
$data['entity_second_own_bundle user_view_own'] = [$entity_second_own_bundle->reveal(), 'view', $user_view_own->reveal(), FALSE];
// View, update, delete permissions.
foreach (['view', 'update', 'delete'] as $operation) {
// Owner, non-owner, user with "any" permission.
$first_user = $this->buildMockUser(6, $operation . ' own green_entity');
$second_user = $this->buildMockUser(7, $operation . ' own green_entity');
$third_user = $this->buildMockUser(8, $operation . ' any green_entity');
$data['entity_first_other user_view_bundle_any'] = [$entity_first_other->reveal(), 'view', $user_view_bundle_any->reveal(), TRUE];
$data['entity_first_own user_view_bundle_any'] = [$entity_first_own->reveal(), 'view', $user_view_bundle_any->reveal(), TRUE];
$data['entity_first_own_bundle user_view_bundle_any'] = [$entity_first_own_bundle->reveal(), 'view', $user_view_bundle_any->reveal(), TRUE];
$data['entity_second_other user_view_bundle_any'] = [$entity_second_other->reveal(), 'view', $user_view_bundle_any->reveal(), FALSE];
$data['entity_second_own user_view_bundle_any'] = [$entity_second_own->reveal(), 'view', $user_view_bundle_any->reveal(), FALSE];
$data['entity_second_own_bundle user_view_bundle_any'] = [$entity_second_own_bundle->reveal(), 'view', $user_view_bundle_any->reveal(), FALSE];
$data[] = [$entity->reveal(), $operation, $first_user->reveal(), TRUE];
$data[] = [$entity->reveal(), $operation, $second_user->reveal(), FALSE];
$data[] = [$entity->reveal(), $operation, $third_user->reveal(), TRUE];
}
$data['entity_first_other user_view_bundle_any'] = [$entity_first_other->reveal(), 'view', $user_view_bundle_own->reveal(), FALSE];
$data['entity_first_own user_view_bundle_any'] = [$entity_first_own->reveal(), 'view', $user_view_bundle_own->reveal(), FALSE];
$data['entity_first_own_bundle user_view_bundle_any'] = [$entity_first_own_bundle->reveal(), 'view', $user_view_bundle_own->reveal(), TRUE];
$data['entity_second_other user_view_bundle_any'] = [$entity_second_other->reveal(), 'view', $user_view_bundle_own->reveal(), FALSE];
$data['entity_second_own user_view_bundle_any'] = [$entity_second_own->reveal(), 'view', $user_view_bundle_own->reveal(), FALSE];
$data['entity_second_own_bundle user_view_bundle_any'] = [$entity_second_own_bundle->reveal(), 'view', $user_view_bundle_own->reveal(), FALSE];
// Per bundle and unpublished view permissions.
$first_user = $this->buildMockUser(11, 'view any first green_entity');
$second_user = $this->buildMockUser(12, 'view own first green_entity');
$third_user = $this->buildMockUser(13, 'view own unpublished green_entity');
// Test the unpublished permissions.
$entity_first_other_up = $this->buildMockEntity($entity_type->reveal(), 9999, 'first', FALSE);
$entity_first_own_up = $this->buildMockEntity($entity_type->reveal(), 14, 'first', FALSE);
$entity_first_own_bundle_up = $this->buildMockEntity($entity_type->reveal(), 15, 'first', FALSE);
$first_entity = $this->buildMockEntity($entity_type->reveal(), 9999, 'first');
$second_entity = $this->buildMockEntity($entity_type->reveal(), 12, 'first');
$third_entity = $this->buildMockEntity($entity_type->reveal(), 9999, 'second');
$fourth_entity = $this->buildMockEntity($entity_type->reveal(), 10, 'second');
$fifth_entity = $this->buildMockEntity($entity_type->reveal(), 13, 'first', FALSE);
$entity_second_other_up = $this->buildMockEntity($entity_type->reveal(), 9999, 'second', FALSE);
$entity_second_own_up = $this->buildMockEntity($entity_type->reveal(), 14, 'second', FALSE);
$entity_second_own_bundle_up = $this->buildMockEntity($entity_type->reveal(), 15, 'second', FALSE);
// The first user can view the two entities of bundle "first".
$data[] = [$first_entity->reveal(), 'view', $first_user->reveal(), TRUE];
$data[] = [$second_entity->reveal(), 'view', $first_user->reveal(), TRUE];
$data[] = [$third_entity->reveal(), 'view', $first_user->reveal(), FALSE];
$data[] = [$fourth_entity->reveal(), 'view', $first_user->reveal(), FALSE];
$data[] = [$fifth_entity->reveal(), 'view', $first_user->reveal(), FALSE];
$user_view_own_up = $this->buildMockUser(14, 'view own unpublished green_entity');
$user_view_other = $this->buildMockUser(15, 'view green_entity');
// The second user can view their own entity of bundle "first".
$data[] = [$first_entity->reveal(), 'view', $second_user->reveal(), FALSE];
$data[] = [$second_entity->reveal(), 'view', $second_user->reveal(), TRUE];
$data[] = [$third_entity->reveal(), 'view', $second_user->reveal(), FALSE];
$data[] = [$fourth_entity->reveal(), 'view', $second_user->reveal(), FALSE];
$data[] = [$fourth_entity->reveal(), 'view', $second_user->reveal(), FALSE];
$data[] = [$fifth_entity->reveal(), 'view', $second_user->reveal(), FALSE];
$data['entity_first_other_up user_view_own_up'] = [$entity_first_other_up->reveal(), 'view', $user_view_own_up->reveal(), FALSE];
$data['entity_first_own_up user_view_own_up'] = [$entity_first_own_up->reveal(), 'view', $user_view_own_up->reveal(), TRUE];
$data['entity_first_own_bundle_up user_view_own_up'] = [$entity_first_own_bundle_up->reveal(), 'view', $user_view_own_up->reveal(), FALSE];
$data['entity_second_other_up user_view_own_up'] = [$entity_second_other_up->reveal(), 'view', $user_view_own_up->reveal(), FALSE];
$data['entity_second_own_up user_view_own_up'] = [$entity_second_own_up->reveal(), 'view', $user_view_own_up->reveal(), TRUE];
$data['entity_second_own_bundle_up user_view_own_up'] = [$entity_second_own_bundle_up->reveal(), 'view', $user_view_own_up->reveal(), FALSE];
$data['entity_first_other_up user_view_other'] = [$entity_first_other_up->reveal(), 'view', $user_view_other->reveal(), FALSE];
$data['entity_first_own_up user_view_other'] = [$entity_first_own_up->reveal(), 'view', $user_view_other->reveal(), FALSE];
$data['entity_first_own_bundle_up user_view_other'] = [$entity_first_own_bundle_up->reveal(), 'view', $user_view_other->reveal(), FALSE];
$data['entity_second_other_up user_view_other'] = [$entity_second_other_up->reveal(), 'view', $user_view_other->reveal(), FALSE];
$data['entity_second_own_up user_view_other'] = [$entity_second_own_up->reveal(), 'view', $user_view_other->reveal(), FALSE];
$data['entity_second_own_bundle_up user_view_other'] = [$entity_second_own_bundle_up->reveal(), 'view', $user_view_other->reveal(), FALSE];
// The third user can only view their own unpublished entity.
$data[] = [$first_entity->reveal(), 'view', $third_user->reveal(), FALSE];
$data[] = [$second_entity->reveal(), 'view', $third_user->reveal(), FALSE];
$data[] = [$third_entity->reveal(), 'view', $third_user->reveal(), FALSE];
$data[] = [$fourth_entity->reveal(), 'view', $third_user->reveal(), FALSE];
$data[] = [$fourth_entity->reveal(), 'view', $third_user->reveal(), FALSE];
$data[] = [$fifth_entity->reveal(), 'view', $third_user->reveal(), TRUE];
return $data;
}
@@ -217,29 +166,19 @@ class UncacheableEntityAccessControlHandlerTest extends UnitTestCase {
$entity_type->getHandlerClass('permission_provider')->willReturn(UncacheableEntityPermissionProvider::class);
// User with the admin permission.
$account = $this->prophesize(AccountInterface::class);
$account->id()->willReturn(6);
$account->hasPermission('administer green_entity')->willReturn(TRUE);
$account = $this->buildMockUser('6', 'administer green_entity');
$data[] = [$entity_type->reveal(), NULL, $account->reveal(), TRUE];
// Ordinary user.
$account = $this->prophesize(AccountInterface::class);
$account->id()->willReturn(6);
$account->hasPermission('create green_entity')->willReturn(TRUE);
$account->hasPermission(Argument::any())->willReturn(FALSE);
$account = $this->buildMockUser('6', 'create green_entity');
$data[] = [$entity_type->reveal(), NULL, $account->reveal(), TRUE];
// Ordinary user, entity with a bundle.
$account = $this->prophesize(AccountInterface::class);
$account->id()->willReturn(6);
$account->hasPermission('create first_bundle green_entity')->willReturn(TRUE);
$account->hasPermission(Argument::any())->willReturn(FALSE);
$account = $this->buildMockUser('6', 'create first_bundle green_entity');
$data[] = [$entity_type->reveal(), 'first_bundle', $account->reveal(), TRUE];
// User with no permissions.
$account = $this->prophesize(AccountInterface::class);
$account->id()->willReturn(6);
$account->hasPermission(Argument::any())->willReturn(FALSE);
$account = $this->buildMockUser('6', 'access content');
$data[] = [$entity_type->reveal(), NULL, $account->reveal(), FALSE];
return $data;
@@ -252,6 +191,10 @@ class UncacheableEntityAccessControlHandlerTest extends UnitTestCase {
* The entity type.
* @param string $owner_id
* The owner ID.
* @param string $bundle
* The bundle.
* @param bool $published
* Whether the entity is published.
*
* @return \Prophecy\Prophecy\ObjectProphecy
* The entity mock.
@@ -284,15 +227,26 @@ class UncacheableEntityAccessControlHandlerTest extends UnitTestCase {
$entity->getCacheTags()->willReturn([]);
$entity->getCacheMaxAge()->willReturn(Cache::PERMANENT);
return $entity;
}
/**
* Builds a mock user.
*
* @param int $uid
* The user ID.
* @param string $permission
* The permission to grant.
*
* @return \Prophecy\Prophecy\ObjectProphecy
* The user mock.
*/
protected function buildMockUser($uid, $permission) {
$account = $this->prophesize(AccountInterface::class);
$account->id()->willReturn($uid);
$account->hasPermission($permission)->willReturn(TRUE);
$account->hasPermission(Argument::any())->willReturn(FALSE);
return $account;
}
@@ -72,12 +72,12 @@ class UncacheableEntityPermissionProviderTest extends UnitTestCase {
$entity_type->id()->willReturn('green_entity');
$entity_type->getSingularLabel()->willReturn('green entity');
$entity_type->getPluralLabel()->willReturn('green entities');
$entity_type->hasLinkTemplate('collection')->willReturn(FALSE);
$entity_type->entityClassImplements(EntityOwnerInterface::class)->willReturn(FALSE);
$entity_type->entityClassImplements(EntityPublishedInterface::class)->willReturn(FALSE);
$entity_type->getPermissionGranularity()->willReturn('entity_type');
$expected_permissions = [
'administer green_entity' => 'Administer green entities',
'access green_entity overview' => 'Access the green entities overview page',
'create green_entity' => 'Create green entities',
'update green_entity' => 'Update green entities',
'delete green_entity' => 'Delete green entities',
@@ -91,6 +91,7 @@ class UncacheableEntityPermissionProviderTest extends UnitTestCase {
$entity_type->id()->willReturn('blue_entity');
$entity_type->getSingularLabel()->willReturn('blue entity');
$entity_type->getPluralLabel()->willReturn('blue entities');
$entity_type->hasLinkTemplate('collection')->willReturn(TRUE);
$entity_type->entityClassImplements(EntityOwnerInterface::class)->willReturn(TRUE);
$entity_type->entityClassImplements(EntityPublishedInterface::class)->willReturn(FALSE);
$entity_type->getPermissionGranularity()->willReturn('entity_type');
@@ -113,6 +114,7 @@ class UncacheableEntityPermissionProviderTest extends UnitTestCase {
$entity_type->id()->willReturn('white_entity');
$entity_type->getSingularLabel()->willReturn('white entity');
$entity_type->getPluralLabel()->willReturn('white entities');
$entity_type->hasLinkTemplate('collection')->willReturn(TRUE);
$entity_type->entityClassImplements(EntityOwnerInterface::class)->willReturn(FALSE);
$entity_type->entityClassImplements(EntityPublishedInterface::class)->willReturn(FALSE);
$entity_type->getPermissionGranularity()->willReturn('bundle');
@@ -137,6 +139,7 @@ class UncacheableEntityPermissionProviderTest extends UnitTestCase {
$entity_type->id()->willReturn('black_entity');
$entity_type->getSingularLabel()->willReturn('black entity');
$entity_type->getPluralLabel()->willReturn('black entities');
$entity_type->hasLinkTemplate('collection')->willReturn(TRUE);
$entity_type->entityClassImplements(EntityOwnerInterface::class)->willReturn(TRUE);
$entity_type->entityClassImplements(EntityPublishedInterface::class)->willReturn(FALSE);
$entity_type->getPermissionGranularity()->willReturn('bundle');
@@ -161,6 +164,7 @@ class UncacheableEntityPermissionProviderTest extends UnitTestCase {
$entity_type->id()->willReturn('pink_entity');
$entity_type->getSingularLabel()->willReturn('pink entity');
$entity_type->getPluralLabel()->willReturn('pink entities');
$entity_type->hasLinkTemplate('collection')->willReturn(TRUE);
$entity_type->entityClassImplements(EntityOwnerInterface::class)->willReturn(TRUE);
$entity_type->entityClassImplements(EntityPublishedInterface::class)->willReturn(TRUE);
$entity_type->getPermissionGranularity()->willReturn('bundle');