diff --git a/core/includes/entity.inc b/core/includes/entity.inc index bdc89f1..b3171ea 100644 --- a/core/includes/entity.inc +++ b/core/includes/entity.inc @@ -305,6 +305,27 @@ function entity_page_label(EntityInterface $entity, $langcode = NULL) { } /** + * Returns the entity access controller for the given entity type. + * + * @param string $entity_type + * The type of the entity. + * + * @return \Drupal\Core\Entity\EntityAccessControllerInterface + * An entity access controller instance. + * + * @see \Drupal\Core\Entity\EntityManager + */ +function entity_access_controller($entity_type) { + $controllers = &drupal_static(__FUNCTION__, array()); + if (!isset($controllers[$entity_type])) { + $type_info = entity_get_info($entity_type); + $class = $type_info['access_controller_class']; + $controllers[$entity_type] = new $class($entity_type); + } + return $controllers[$entity_type]; +} + +/** * Returns an entity form controller for the given operation. * * Since there might be different scenarios in which an entity is edited, diff --git a/core/lib/Drupal/Core/Entity/Entity.php b/core/lib/Drupal/Core/Entity/Entity.php index c6e041e..979614f 100644 --- a/core/lib/Drupal/Core/Entity/Entity.php +++ b/core/lib/Drupal/Core/Entity/Entity.php @@ -251,8 +251,9 @@ public function getIterator() { /** * Implements AccessibleInterface::access(). */ - public function access(\Drupal\user\User $account = NULL) { - // TODO: Implement access() method. + public function access($operation = 'view', \Drupal\user\Plugin\Core\Entity\User $account = NULL) { + $method = $operation . 'Access'; + return entity_access_controller($this->entityType)->$method($this, LANGUAGE_DEFAULT, $account); } /** diff --git a/core/lib/Drupal/Core/Entity/EntityAccessController.php b/core/lib/Drupal/Core/Entity/EntityAccessController.php new file mode 100644 index 0000000..2f13110 --- /dev/null +++ b/core/lib/Drupal/Core/Entity/EntityAccessController.php @@ -0,0 +1,48 @@ + 'Drupal\Core\Entity\EntityListController', 'render_controller_class' => 'Drupal\Core\Entity\EntityRenderController', + 'access_controller_class' => 'Drupal\Core\Entity\EntityAccessController', 'static_cache' => TRUE, 'translation' => array(), 'bundles' => array(), diff --git a/core/lib/Drupal/Core/Entity/Field/Type/EntityTranslation.php b/core/lib/Drupal/Core/Entity/Field/Type/EntityTranslation.php index 0db4657..280fca9 100644 --- a/core/lib/Drupal/Core/Entity/Field/Type/EntityTranslation.php +++ b/core/lib/Drupal/Core/Entity/Field/Type/EntityTranslation.php @@ -233,8 +233,9 @@ public function isEmpty() { /** * Implements AccessibleInterface::access(). */ - public function access(\Drupal\user\User $account = NULL) { - // @todo implement + public function access($operation = 'view', \Drupal\user\Plugin\Core\Entity\User $account = NULL) { + $method = $operation . 'Access'; + return entity_access_controller($this->parent->entityType())->$method($this->parent, $this->langcode, $account); } /** diff --git a/core/lib/Drupal/Core/Entity/Field/Type/Field.php b/core/lib/Drupal/Core/Entity/Field/Type/Field.php index e7a5960..cc5951c 100644 --- a/core/lib/Drupal/Core/Entity/Field/Type/Field.php +++ b/core/lib/Drupal/Core/Entity/Field/Type/Field.php @@ -10,7 +10,7 @@ use Drupal\Core\Entity\Field\FieldInterface; use Drupal\Core\TypedData\TypedDataInterface; use Drupal\Core\TypedData\Type\TypedData; -use Drupal\user\User; +use Drupal\user\Plugin\Core\Entity\User; use ArrayIterator; use IteratorAggregate; use InvalidArgumentException; @@ -296,7 +296,7 @@ public function __clone() { /** * Implements AccessibleInterface::access(). */ - public function access(User $account = NULL) { + public function access($operation = 'view', User $account = NULL) { // TODO: Implement access() method. Use item access. } } diff --git a/core/lib/Drupal/Core/TypedData/AccessibleInterface.php b/core/lib/Drupal/Core/TypedData/AccessibleInterface.php index 669f1d7..d120584 100644 --- a/core/lib/Drupal/Core/TypedData/AccessibleInterface.php +++ b/core/lib/Drupal/Core/TypedData/AccessibleInterface.php @@ -15,12 +15,23 @@ /** * Checks data value access. * - * @param \Drupal\user\User $account - * (optional) The user account to check access for. Defaults to the current - * user. + * @param string $operation + * (optional) The operation to be performed. Supported values are: + * - view + * - create + * - update + * - delete + * Defaults to 'view'. + * @param \Drupal\user\Plugin\Core\Entity\User $account + * (optional) The user for which to check access, or NULL to check access + * for the current user. Defaults to NULL. * * @return bool - * TRUE if the given user has access; otherwise FALSE. + * TRUE if the given user has access for the given operation, FALSE + * otherwise. + * + * @todo Don't depend on module level code. */ - public function access(\Drupal\user\User $account = NULL); + public function access($operation = 'view', \Drupal\user\Plugin\Core\Entity\User $account = NULL); + } diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityAccessTest.php b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityAccessTest.php new file mode 100644 index 0000000..1b40c2d --- /dev/null +++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityAccessTest.php @@ -0,0 +1,133 @@ + 'Entity access', + 'description' => 'Tests entity access.', + 'group' => 'Entity API', + ); + } + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = array('entity_test'); + + /** + * Asserts entity access correctly grants or denies access. + */ + function assertEntityAccess($ops, AccessibleInterface $object, User $account = NULL) { + foreach ($ops as $op => $result) { + $message = format_string("Entity access returns @result with operation '@op'.", array( + '@result' => isset($result) ? 'null' : ($result ? 'true' : 'false'), + '@op' => $op, + )); + + $this->assertEqual($result, $object->access($op, $account), $message); + } + } + + /** + * Ensures entity access is properly working. + */ + function testEntityAccess() { + // Set up a non-admin user that is allowed to view test entities. + $user = $this->drupalCreateUser(array('view test entity')); + $this->drupalLogin($user); + + $entity = entity_create('entity_test', array( + 'name' => 'test', + )); + $entity->save(); + + // The current user is allowed to view, create, update and delete entities. + $this->assertEntityAccess(array( + 'create' => TRUE, + 'update' => TRUE, + 'delete' => TRUE, + 'view' => TRUE, + ), $entity); + + // The custom user is not allowed to view test entities. + $custom_user = $this->drupalCreateUser(); + $this->assertEntityAccess(array( + 'create' => TRUE, + 'update' => TRUE, + 'delete' => TRUE, + 'view' => FALSE, + ), $entity, $custom_user); + } + + /** + * Ensures that the default controller is used as a fallback. + */ + function testEntityAccessDefaultController() { + // Remove the access controller definition from the test entity. + state()->set('entity_test.default_access_controller', TRUE); + + // Check that the default access controller is used for entities that don't + // have a specific access controller defined. + $controller = entity_access_controller('entity_test'); + $this->assertTrue($controller instanceof \Drupal\Core\Entity\EntityAccessController, 'The default entity controller is used for the entity_test entity type.'); + + $entity = entity_create('entity_test', array()); + $this->assertEntityAccess(array( + 'create' => FALSE, + 'update' => FALSE, + 'delete' => FALSE, + 'view' => FALSE, + ), $entity); + } + + /** + * Ensures entity access for entity translations is properly working. + */ + function testEntityTranslationAccess() { + // Enable translations for the test entity type. + variable_set('entity_test_translation', TRUE); + module_enable(array('locale')); + + // Set up a non-admin user that is allowed to view test entity translations. + $user = $this->drupalCreateUser(array('view test entity translations')); + $this->drupalLogin($user); + + // Create two test languages. + foreach (array('foo', 'bar') as $langcode) { + $language = new Language(array( + 'langcode' => $langcode, + 'name' => $this->randomString(), + )); + language_save($language); + } + + $entity = entity_create('entity_test', array( + 'name' => 'test', + 'langcode' => 'foo', + )); + $entity->save(); + + $translation = $entity->getTranslation('bar'); + $this->assertEntityAccess(array( + 'view' => TRUE, + ), $translation); + } +} diff --git a/core/modules/system/tests/modules/entity_test/entity_test.module b/core/modules/system/tests/modules/entity_test/entity_test.module index 7844dcb..403c1b9 100644 --- a/core/modules/system/tests/modules/entity_test/entity_test.module +++ b/core/modules/system/tests/modules/entity_test/entity_test.module @@ -16,6 +16,10 @@ function entity_test_entity_info_alter(&$info) { if (variable_get('entity_test_translation')) { $info['entity_test']['translation']['entity_test'] = TRUE; } + // Optionally unset the access controller to test the fallback. + if (state()->get('entity_test.default_access_controller')) { + unset($info['entity_test']['access_controller_class']); + } } /** @@ -27,6 +31,12 @@ function entity_test_permission() { 'title' => t('Administer entity_test content'), 'description' => t('Manage entity_test content'), ), + 'view test entity' => array( + 'title' => t('View test entities'), + ), + 'view test entity translations' => array( + 'title' => t('View translations of test entities'), + ), ); return $permissions; } diff --git a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestAccessController.php b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestAccessController.php new file mode 100644 index 0000000..d79fa68 --- /dev/null +++ b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestAccessController.php @@ -0,0 +1,50 @@ +__call(__FUNCTION__, func_get_args()); }