Commit ee5b4a70 authored by catch's avatar catch

Issue #1883152 by klausi: Field level access for EntityNG.

parent 7a411926
......@@ -512,3 +512,56 @@ function hook_entity_field_info_alter(&$info, $entity_type) {
$info['definitions']['mymodule_text']['class'] = '\Drupal\anothermodule\EntityComputedText';
}
}
/**
* Control access to fields.
*
* This hook is invoked from \Drupal\Core\Entity\Field\Type\Field::access() to
* let modules grant or deny operations on fields.
*
* @param string $operation
* The operation to be performed. See
* \Drupal\Core\TypedData\AccessibleInterface::access() for possible values.
* @param \Drupal\Core\Entity\Field\Type\Field $field
* The entity field object on which the operation is to be performed.
* @param \Drupal\user\Plugin\Core\Entity\User $account
* The user account to check.
*
* @return bool|NULL
* TRUE if access should be allowed, FALSE if access should be denied and NULL
* if the implementation has no opinion.
*/
function hook_entity_field_access($operation, $field, $account) {
if ($field->getName() == 'field_of_interest' && $operation == 'update') {
return user_access('update field of interest', $account);
}
}
/**
* Alters the default access behaviour for a given field.
*
* Use this hook to override access grants from another module. Note that the
* original default access flag is masked under the ':default' key.
*
* @param array $grants
* An array of grants gathered by hook_entity_field_access(). The array is
* keyed by the module that defines the field's access control; the values are
* grant responses for each module (Boolean or NULL).
* @param array $context
* Context array on the performed operation with the following keys:
* - operation: The operation to be performed (string).
* - field: The entity field object (\Drupal\Core\Entity\Field\Type\Field).
* - account: The user account to check access for
* (Drupal\user\Plugin\Core\Entity\User).
*/
function hook_entity_field_access_alter(array &$grants, array $context) {
$field = $context['field'];
if ($field->getName() == 'field_of_interest' && $grants['node'] === FALSE) {
// Override node module's restriction to no opinion. We don't want to
// provide our own access hook, we only want to take out node module's part
// in the access handling of this field. We also don't want to switch node
// module's grant to TRUE, because the grants of other modules should still
// decide on their own if this field is accessible or not.
$grants['node'] = NULL;
}
}
......@@ -149,6 +149,52 @@ public function __unset($property_name) {
* Implements \Drupal\Core\TypedData\AccessibleInterface::access().
*/
public function access($operation = 'view', User $account = NULL) {
// TODO: Implement access() method. Use item access.
global $user;
if (!isset($account) && $user->uid) {
$account = user_load($user->uid);
}
// Get the default access restriction that lives within this field.
$access = $this->defaultAccess($operation, $account);
// Invoke hook and collect grants/denies for field access from other
// modules. Our default access flag is masked under the ':default' key.
$grants = array(':default' => $access);
$hook_implementations = drupal_container()->get('module_handler')->getImplementations('entity_field_access');
foreach ($hook_implementations as $module) {
$grants = array_merge($grants, array($module => module_invoke($module, 'entity_field_access', $operation, $this, $account)));
}
// Also allow modules to alter the returned grants/denies.
$context = array(
'operation' => $operation,
'field' => $this,
'account' => $account,
);
drupal_alter('entity_field_access', $grants, $context);
// One grant being FALSE is enough to deny access immediately.
if (in_array(FALSE, $grants, TRUE)) {
return FALSE;
}
// At least one grant has the explicit opinion to allow access.
if (in_array(TRUE, $grants, TRUE)) {
return TRUE;
}
// All grants are NULL and have no opinion - deny access in that case.
return FALSE;
}
/**
* Contains the default access logic of this field.
*
* See \Drupal\Core\TypedData\AccessibleInterface::access() for the parameter
* doucmentation. This method can be overriden by field sub classes to provide
* a different default access logic. That allows them to inherit the complete
* access() method which contains the access hook invocation logic.
*
* @return bool
* TRUE if access to this field is allowed per default, FALSE otherwise.
*/
public function defaultAccess($operation = 'view', User $account = NULL) {
// Grant access per default.
return TRUE;
}
}
<?php
/**
* @file
* Contains \Drupal\system\Tests\Entity\FieldAccessTest.
*/
namespace Drupal\system\Tests\Entity;
use Drupal\simpletest\DrupalUnitTestBase;
/**
* Tests the functionality of field access.
*/
class FieldAccessTest extends DrupalUnitTestBase {
/**
* Modules to load code from.
*
* @var array
*/
public static $modules = array('entity_test', 'field', 'field_sql_storage', 'system', 'text', 'user');
/**
* Holds the currently active global user ID that initiated the test run.
*
* The user ID gets replaced during the test and needs to be kept here so that
* it can be restored at the end of the test run.
*
* @var int
*/
protected $activeUid;
public static function getInfo() {
return array(
'name' => 'Field access tests',
'description' => 'Test Field level access hooks.',
'group' => 'Entity API',
);
}
protected function setUp() {
parent::setUp();
// Install field module schema.
$this->installSchema('field', array('field_config', 'field_config_instance'));
// The users table is needed for creating dummy user accounts.
$this->installSchema('user', array('users'));
// Register entity_test text field.
entity_test_install();
}
/**
* Tests hook_entity_field_access() and hook_entity_field_access_alter().
*
* @see entity_test_entity_field_access()
* @see entity_test_entity_field_access_alter()
*/
function testFieldAccess() {
$values = array(
'name' => $this->randomName(),
'user_id' => 1,
'field_test_text' => array(
'value' => 'no access value',
'format' => 'full_html',
),
);
$entity = entity_create('entity_test', $values);
// Create a dummy user account for testing access with.
$values = array('name' => 'test');
$account = entity_create('user', $values);
$this->assertFalse($entity->field_test_text->access('view', $account), 'Access to the field was denied.');
$entity->field_test_text = 'access alter value';
$this->assertFalse($entity->field_test_text->access('view', $account), 'Access to the field was denied.');
$entity->field_test_text = 'standard value';
$this->assertTrue($entity->field_test_text->access('view', $account), 'Access to the field was granted.');
}
}
......@@ -282,3 +282,26 @@ function entity_test_entity_view_mode_info() {
return $view_modes;
}
/**
* Implements hook_entity_field_access().
*
* @see \Drupal\system\Tests\Entity\FieldAccessTest::testFieldAccess()
*/
function entity_test_entity_field_access($operation, $field, $account) {
if ($field->getName() == 'field_test_text' && $field->value == 'no access value') {
return FALSE;
}
}
/**
* Implements hook_entity_field_access_alter().
*
* @see \Drupal\system\Tests\Entity\FieldAccessTest::testFieldAccess()
*/
function entity_test_entity_field_access_alter(array &$grants, array $context) {
$field = $context['field'];
if ($field->getName() == 'field_test_text' && $field->value == 'access alter value') {
$grants[':default'] = FALSE;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment