Commit e20d78e1 authored by Dries's avatar Dries

Issue #2248977 by Berdir: Complete support for multi-value base fields in...

Issue #2248977 by Berdir: Complete support for multi-value base fields in ContentEntitySchemaHandler and use it for the user.roles field
parent 688fab7b
...@@ -8,11 +8,9 @@ ...@@ -8,11 +8,9 @@
namespace Drupal\Core\Entity\Query\Sql; namespace Drupal\Core\Entity\Query\Sql;
use Drupal\Core\Database\Query\SelectInterface; use Drupal\Core\Database\Query\SelectInterface;
use Drupal\Core\Entity\ContentEntityTypeInterface;
use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\Query\QueryException; use Drupal\Core\Entity\Query\QueryException;
use Drupal\Core\Entity\Sql\SqlEntityStorageInterface; use Drupal\Core\Entity\Sql\SqlEntityStorageInterface;
use Drupal\field\FieldStorageConfigInterface;
/** /**
* Adds tables and fields to the SQL entity query. * Adds tables and fields to the SQL entity query.
...@@ -80,12 +78,7 @@ public function addField($field, $type, $langcode) { ...@@ -80,12 +78,7 @@ public function addField($field, $type, $langcode) {
$propertyDefinitions = array(); $propertyDefinitions = array();
$entity_type = $this->entityManager->getDefinition($entity_type_id); $entity_type = $this->entityManager->getDefinition($entity_type_id);
$field_storage_definitions = array(); $field_storage_definitions = $this->entityManager->getFieldStorageDefinitions($entity_type_id);
// @todo Needed for menu links, make this implementation content entity
// specific after https://drupal.org/node/2256521.
if ($entity_type instanceof ContentEntityTypeInterface) {
$field_storage_definitions = $this->entityManager->getFieldStorageDefinitions($entity_type_id);
}
for ($key = 0; $key <= $count; $key ++) { for ($key = 0; $key <= $count; $key ++) {
// If there is revision support and only the current revision is being // If there is revision support and only the current revision is being
// queried then use the revision id. Otherwise, the entity id will do. // queried then use the revision id. Otherwise, the entity id will do.
...@@ -110,12 +103,14 @@ public function addField($field, $type, $langcode) { ...@@ -110,12 +103,14 @@ public function addField($field, $type, $langcode) {
else { else {
$field_storage = FALSE; $field_storage = FALSE;
} }
// If we managed to retrieve a configurable field, process it.
if ($field_storage instanceof FieldStorageConfigInterface) { /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */
$table_mapping = $this->entityManager->getStorage($entity_type_id)->getTableMapping();
// Check whether this field is stored in a dedicated table.
if ($field_storage && $table_mapping->requiresDedicatedTableStorage($field_storage)) {
// Find the field column. // Find the field column.
$column = $field_storage->getMainPropertyName(); $column = $field_storage->getMainPropertyName();
/** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */
$table_mapping = $this->entityManager->getStorage($entity_type_id)->getTableMapping();
if ($key < $count) { if ($key < $count) {
$next = $specifiers[$key + 1]; $next = $specifiers[$key + 1];
...@@ -145,7 +140,7 @@ public function addField($field, $type, $langcode) { ...@@ -145,7 +140,7 @@ public function addField($field, $type, $langcode) {
$table = $this->ensureFieldTable($index_prefix, $field_storage, $type, $langcode, $base_table, $entity_id_field, $field_id_field); $table = $this->ensureFieldTable($index_prefix, $field_storage, $type, $langcode, $base_table, $entity_id_field, $field_id_field);
$sql_column = $table_mapping->getFieldColumnName($field_storage, $column); $sql_column = $table_mapping->getFieldColumnName($field_storage, $column);
} }
// This is an entity base field (non-configurable field). // The field is stored in a shared table.
else { else {
// ensureEntityTable() decides whether an entity property will be // ensureEntityTable() decides whether an entity property will be
// queried from the data table or the base table based on where it // queried from the data table or the base table based on where it
......
...@@ -92,7 +92,7 @@ public function read($sid) { ...@@ -92,7 +92,7 @@ public function read($sid) {
// active user. // active user.
if ($values && $values['uid'] > 0 && $values['status'] == 1) { if ($values && $values['uid'] > 0 && $values['status'] == 1) {
// Add roles element to $user. // Add roles element to $user.
$rids = $this->connection->query("SELECT ur.rid FROM {users_roles} ur WHERE ur.uid = :uid", array( $rids = $this->connection->query("SELECT ur.roles_target_id as rid FROM {user__roles} ur WHERE ur.entity_id = :uid", array(
':uid' => $values['uid'], ':uid' => $values['uid'],
))->fetchCol(); ))->fetchCol();
$values['roles'] = array_merge(array(DRUPAL_AUTHENTICATED_RID), $rids); $values['roles'] = array_merge(array(DRUPAL_AUTHENTICATED_RID), $rids);
......
...@@ -126,7 +126,7 @@ public function getRoles($exclude_locked_roles = FALSE) { ...@@ -126,7 +126,7 @@ public function getRoles($exclude_locked_roles = FALSE) {
$roles = $this->roles; $roles = $this->roles;
if ($exclude_locked_roles) { if ($exclude_locked_roles) {
$roles = array_diff($roles, array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID)); $roles = array_values(array_diff($roles, array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID)));
} }
return $roles; return $roles;
......
...@@ -184,7 +184,7 @@ function testFormatPermissions() { ...@@ -184,7 +184,7 @@ function testFormatPermissions() {
*/ */
function testFormatRoles() { function testFormatRoles() {
// Get the role ID assigned to the regular user. // Get the role ID assigned to the regular user.
$roles = $this->web_user->getRoles(); $roles = $this->web_user->getRoles(TRUE);
$rid = $roles[0]; $rid = $roles[0];
// Check that this role appears in the list of roles that have access to an // Check that this role appears in the list of roles that have access to an
......
...@@ -143,7 +143,7 @@ function testSearchResultsComment() { ...@@ -143,7 +143,7 @@ function testSearchResultsComment() {
function testSearchResultsCommentAccess() { function testSearchResultsCommentAccess() {
$comment_body = 'Test comment body'; $comment_body = 'Test comment body';
$this->comment_subject = 'Test comment subject'; $this->comment_subject = 'Test comment subject';
$roles = $this->admin_user->getRoles(); $roles = $this->admin_user->getRoles(TRUE);
$this->admin_role = $roles[0]; $this->admin_role = $roles[0];
// Create a node. // Create a node.
......
...@@ -75,8 +75,7 @@ protected function setUp() { ...@@ -75,8 +75,7 @@ protected function setUp() {
* @param array $values * @param array $values
* (optional) The values used to create the entity. * (optional) The values used to create the entity.
* @param array $permissions * @param array $permissions
* (optional) Array of permission names to assign to user. The * (optional) Array of permission names to assign to user.
* users_roles tables must be installed before this can be used.
* *
* @return \Drupal\user\Entity\User * @return \Drupal\user\Entity\User
* The created user entity. * The created user entity.
......
...@@ -294,10 +294,10 @@ display: ...@@ -294,10 +294,10 @@ display:
type_custom_false: '' type_custom_false: ''
not: '0' not: '0'
plugin_id: boolean plugin_id: boolean
rid: roles_target_id:
id: rid id: roles_target_id
table: users_roles table: user__roles
field: rid field: roles_target_id
relationship: none relationship: none
group_type: group group_type: group
admin_label: '' admin_label: ''
...@@ -552,7 +552,7 @@ display: ...@@ -552,7 +552,7 @@ display:
user_bulk_form: '0' user_bulk_form: '0'
name: '0' name: '0'
status: '0' status: '0'
rid: '0' roles_target_id: '0'
created: '0' created: '0'
access: '0' access: '0'
destination: true destination: true
...@@ -649,10 +649,10 @@ display: ...@@ -649,10 +649,10 @@ display:
name: name name: name
mail: mail mail: mail
plugin_id: combine plugin_id: combine
rid: roles_target_id:
id: rid id: roles_target_id
table: users_roles table: user__roles
field: rid field: roles_target_id
relationship: none relationship: none
group_type: group group_type: group
admin_label: '' admin_label: ''
...@@ -661,11 +661,11 @@ display: ...@@ -661,11 +661,11 @@ display:
group: 1 group: 1
exposed: true exposed: true
expose: expose:
operator_id: rid_op operator_id: roles_target_id_op
label: Role label: Role
description: '' description: ''
use_operator: false use_operator: false
operator: rid_op operator: roles_target_id_op
identifier: role identifier: role
required: false required: false
remember: false remember: false
...@@ -691,7 +691,7 @@ display: ...@@ -691,7 +691,7 @@ display:
plugin_id: user_roles plugin_id: user_roles
permission: permission:
id: permission id: permission
table: users_roles table: user__roles
field: permission field: permission
relationship: none relationship: none
group_type: group group_type: group
......
...@@ -23,7 +23,7 @@ views.argument.user_uid: ...@@ -23,7 +23,7 @@ views.argument.user_uid:
type: views.argument.numeric type: views.argument.numeric
label: 'User ID' label: 'User ID'
views.argument.users_roles_rid: views.argument.user__roles_rid:
type: views.argument.many_to_one type: views.argument.many_to_one
label: 'Role ID' label: 'Role ID'
......
...@@ -70,22 +70,19 @@ public function isNew() { ...@@ -70,22 +70,19 @@ public function isNew() {
return !empty($this->enforceIsNew) || $this->id() === NULL; return !empty($this->enforceIsNew) || $this->id() === NULL;
} }
/**
* {@inheritdoc}
*/
static function preCreate(EntityStorageInterface $storage, array &$values) {
parent::preCreate($storage, $values);
// Users always have the authenticated user role.
$values['roles'][] = DRUPAL_AUTHENTICATED_RID;
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function preSave(EntityStorageInterface $storage) { public function preSave(EntityStorageInterface $storage) {
parent::preSave($storage); parent::preSave($storage);
// Make sure that the authenticated/anonymous roles are not persisted.
foreach ($this->get('roles') as $index => $item) {
if (in_array($item->target_id, array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID))) {
$this->get('roles')->offsetUnset($index);
}
}
// Update the user password if it has changed. // Update the user password if it has changed.
if ($this->isNew() || ($this->pass->value && $this->pass->value != $this->original->pass->value)) { if ($this->isNew() || ($this->pass->value && $this->pass->value != $this->original->pass->value)) {
// Allow alternate password hashing schemes. // Allow alternate password hashing schemes.
...@@ -129,12 +126,6 @@ public function postSave(EntityStorageInterface $storage, $update = TRUE) { ...@@ -129,12 +126,6 @@ public function postSave(EntityStorageInterface $storage, $update = TRUE) {
} }
} }
// Update user roles if changed.
if ($this->getRoles() != $this->original->getRoles()) {
$storage->deleteUserRoles(array($this->id()));
$storage->saveRoles($this);
}
// If the user was blocked, delete the user's sessions to force a logout. // If the user was blocked, delete the user's sessions to force a logout.
if ($this->original->status->value != $this->status->value && $this->status->value == 0) { if ($this->original->status->value != $this->status->value && $this->status->value == 0) {
$session_manager->delete($this->id()); $session_manager->delete($this->id());
...@@ -147,12 +138,6 @@ public function postSave(EntityStorageInterface $storage, $update = TRUE) { ...@@ -147,12 +138,6 @@ public function postSave(EntityStorageInterface $storage, $update = TRUE) {
_user_mail_notify($op, $this); _user_mail_notify($op, $this);
} }
} }
else {
// Save user roles.
if (count($this->getRoles()) > 1) {
$storage->saveRoles($this);
}
}
} }
/** /**
...@@ -163,7 +148,6 @@ public static function postDelete(EntityStorageInterface $storage, array $entiti ...@@ -163,7 +148,6 @@ public static function postDelete(EntityStorageInterface $storage, array $entiti
$uids = array_keys($entities); $uids = array_keys($entities);
\Drupal::service('user.data')->delete(NULL, $uids); \Drupal::service('user.data')->delete(NULL, $uids);
$storage->deleteUserRoles($uids);
} }
/** /**
...@@ -172,8 +156,18 @@ public static function postDelete(EntityStorageInterface $storage, array $entiti ...@@ -172,8 +156,18 @@ public static function postDelete(EntityStorageInterface $storage, array $entiti
public function getRoles($exclude_locked_roles = FALSE) { public function getRoles($exclude_locked_roles = FALSE) {
$roles = array(); $roles = array();
// Users with an ID always have the authenticated user role.
if (!$exclude_locked_roles) {
if ($this->isAuthenticated()) {
$roles[] = DRUPAL_AUTHENTICATED_RID;
}
else {
$roles[] = DRUPAL_ANONYMOUS_RID;
}
}
foreach ($this->get('roles') as $role) { foreach ($this->get('roles') as $role) {
if (!($exclude_locked_roles && in_array($role->target_id, array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID)))) { if ($role->target_id) {
$roles[] = $role->target_id; $roles[] = $role->target_id;
} }
} }
...@@ -223,7 +217,12 @@ public function hasRole($rid) { ...@@ -223,7 +217,12 @@ public function hasRole($rid) {
* {@inheritdoc} * {@inheritdoc}
*/ */
public function addRole($rid) { public function addRole($rid) {
$roles = $this->getRoles();
if (in_array($rid, [DRUPAL_AUTHENTICATED_RID, DRUPAL_ANONYMOUS_RID])) {
throw new \InvalidArgumentException('Anonymous or authenticated role ID must not be assigned manually.');
}
$roles = $this->getRoles(TRUE);
$roles[] = $rid; $roles[] = $rid;
$this->set('roles', array_unique($roles)); $this->set('roles', array_unique($roles));
} }
...@@ -232,7 +231,7 @@ public function addRole($rid) { ...@@ -232,7 +231,7 @@ public function addRole($rid) {
* {@inheritdoc} * {@inheritdoc}
*/ */
public function removeRole($rid) { public function removeRole($rid) {
$this->set('roles', array_diff($this->getRoles(), array($rid))); $this->set('roles', array_diff($this->getRoles(TRUE), array($rid)));
} }
/** /**
...@@ -531,7 +530,6 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { ...@@ -531,7 +530,6 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
->setDefaultValue(''); ->setDefaultValue('');
$fields['roles'] = BaseFieldDefinition::create('entity_reference') $fields['roles'] = BaseFieldDefinition::create('entity_reference')
->setCustomStorage(TRUE)
->setLabel(t('Roles')) ->setLabel(t('Roles'))
->setCardinality(BaseFieldDefinition::CARDINALITY_UNLIMITED) ->setCardinality(BaseFieldDefinition::CARDINALITY_UNLIMITED)
->setDescription(t('The roles the user has.')) ->setDescription(t('The roles the user has.'))
......
...@@ -88,6 +88,12 @@ public function buildEntityQuery($match = NULL, $match_operator = 'CONTAINS') { ...@@ -88,6 +88,12 @@ public function buildEntityQuery($match = NULL, $match_operator = 'CONTAINS') {
$query->condition('name', $match, $match_operator); $query->condition('name', $match, $match_operator);
} }
// Filter by role.
$handler_settings = $this->fieldDefinition->getSetting('handler_settings');
if (!empty($handler_settings['filter']['role'])) {
$query->condition('roles', $handler_settings['filter']['role'], 'IN');
}
// Adding the permission check is sadly insufficient for users: core // Adding the permission check is sadly insufficient for users: core
// requires us to also know about the concept of 'blocked' and 'active'. // requires us to also know about the concept of 'blocked' and 'active'.
if (!\Drupal::currentUser()->hasPermission('administer users')) { if (!\Drupal::currentUser()->hasPermission('administer users')) {
...@@ -131,16 +137,5 @@ public function entityQueryAlter(SelectInterface $query) { ...@@ -131,16 +137,5 @@ public function entityQueryAlter(SelectInterface $query) {
} }
} }
} }
// Add the filter by role option.
if (!empty($this->fieldDefinition->getSetting('handler_settings')['filter'])) {
$filter_settings = $this->fieldDefinition->getSetting('handler_settings')['filter'];
if ($filter_settings['type'] == 'role') {
$tables = $query->getTables();
$base_table = $tables['base_table']['alias'];
$query->join('users_roles', 'ur', $base_table . '.uid = ur.uid');
$query->condition('ur.rid', $filter_settings['role']);
}
}
} }
} }
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
* *
* @ingroup views_argument_handlers * @ingroup views_argument_handlers
* *
* @ViewsArgument("users_roles_rid") * @ViewsArgument("user__roles_rid")
*/ */
class RolesRid extends ManyToOne { class RolesRid extends ManyToOne {
......
...@@ -79,7 +79,7 @@ public function preRender(&$values) { ...@@ -79,7 +79,7 @@ public function preRender(&$values) {
if ($uids) { if ($uids) {
$roles = user_roles(); $roles = user_roles();
$result = $this->database->query('SELECT u.uid, u.rid FROM {users_roles} u WHERE u.uid IN (:uids) AND u.rid IN (:rids)', array(':uids' => $uids, ':rids' => array_keys($roles))); $result = $this->database->query('SELECT u.entity_id as uid, u.roles_target_id as rid FROM {user__roles} u WHERE u.entity_id IN (:uids) AND u.roles_target_id IN (:rids)', array(':uids' => $uids, ':rids' => array_keys($roles)));
foreach ($result as $role) { foreach ($result as $role) {
$this->items[$role->uid][$role->rid]['role'] = String::checkPlain($roles[$role->rid]->label()); $this->items[$role->uid][$role->rid]['role'] = String::checkPlain($roles[$role->rid]->label());
$this->items[$role->uid][$role->rid]['rid'] = $role->rid; $this->items[$role->uid][$role->rid]['rid'] = $role->rid;
......
...@@ -34,8 +34,8 @@ public function isPermissionInRoles($permission, array $rids) { ...@@ -34,8 +34,8 @@ public function isPermissionInRoles($permission, array $rids) {
*/ */
public function deleteRoleReferences(array $rids) { public function deleteRoleReferences(array $rids) {
// Remove the role from all users. // Remove the role from all users.
db_delete('users_roles') db_delete('user__roles')
->condition('rid', $rids) ->condition('target_id', $rids)
->execute(); ->execute();
} }
......
...@@ -28,10 +28,10 @@ function testUserDeleteMultiple() { ...@@ -28,10 +28,10 @@ function testUserDeleteMultiple() {
$uids = array($user_a->id(), $user_b->id(), $user_c->id()); $uids = array($user_a->id(), $user_b->id(), $user_c->id());
// These users should have a role // These users should have a role
$query = db_select('users_roles', 'r'); $query = db_select('user__roles', 'r');
$roles_created = $query $roles_created = $query
->fields('r', array('uid')) ->fields('r', array('entity_id'))
->condition('uid', $uids) ->condition('entity_id', $uids)
->countQuery() ->countQuery()
->execute() ->execute()
->fetchField(); ->fetchField();
...@@ -42,10 +42,10 @@ function testUserDeleteMultiple() { ...@@ -42,10 +42,10 @@ function testUserDeleteMultiple() {
// Delete the users. // Delete the users.
user_delete_multiple($uids); user_delete_multiple($uids);
// Test if the roles assignments are deleted. // Test if the roles assignments are deleted.
$query = db_select('users_roles', 'r'); $query = db_select('user__roles', 'r');
$roles_after_deletion = $query $roles_after_deletion = $query
->fields('r', array('uid')) ->fields('r', array('entity_id'))
->condition('uid', $uids) ->condition('entity_id', $uids)
->countQuery() ->countQuery()
->execute() ->execute()
->fetchField(); ->fetchField();
......
...@@ -78,6 +78,7 @@ function testUserSelectionByRole() { ...@@ -78,6 +78,7 @@ function testUserSelectionByRole() {
$user3->addRole($this->role2->id()); $user3->addRole($this->role2->id());
$user3->save(); $user3->save();
/** @var \Drupal\entity_reference\EntityReferenceAutocomplete $autocomplete */ /** @var \Drupal\entity_reference\EntityReferenceAutocomplete $autocomplete */
$autocomplete = \Drupal::service('entity_reference.autocomplete'); $autocomplete = \Drupal::service('entity_reference.autocomplete');
......
...@@ -39,32 +39,35 @@ public function testUserMethods() { ...@@ -39,32 +39,35 @@ public function testUserMethods() {
$role_storage->create(array('id' => 'test_role_two'))->save(); $role_storage->create(array('id' => 'test_role_two'))->save();
$role_storage->create(array('id' => 'test_role_three'))->save(); $role_storage->create(array('id' => 'test_role_three'))->save();
$values = array('roles' => array(LanguageInterface::LANGCODE_DEFAULT => array('test_role_one'))); $values = array(
$user = new User($values, 'user'); 'uid' => 1,
'roles' => array('test_role_one'),
);
$user = User::create($values);
$this->assertTrue($user->hasRole('test_role_one')); $this->assertTrue($user->hasRole('test_role_one'));
$this->assertFalse($user->hasRole('test_role_two')); $this->assertFalse($user->hasRole('test_role_two'));
$this->assertEqual(array('test_role_one'), $user->getRoles()); $this->assertEqual(array(DRUPAL_AUTHENTICATED_RID, 'test_role_one'), $user->getRoles());
$user->addRole('test_role_one'); $user->addRole('test_role_one');
$this->assertTrue($user->hasRole('test_role_one')); $this->assertTrue($user->hasRole('test_role_one'));
$this->assertFalse($user->hasRole('test_role_two')); $this->assertFalse($user->hasRole('test_role_two'));
$this->assertEqual(array('test_role_one'), $user->getRoles()); $this->assertEqual(array(DRUPAL_AUTHENTICATED_RID, 'test_role_one'), $user->getRoles());
$user->addRole('test_role_two'); $user->addRole('test_role_two');
$this->assertTrue($user->hasRole('test_role_one')); $this->assertTrue($user->hasRole('test_role_one'));
$this->assertTrue($user->hasRole('test_role_two')); $this->assertTrue($user->hasRole('test_role_two'));
$this->assertEqual(array('test_role_one', 'test_role_two'), $user->getRoles()); $this->assertEqual(array(DRUPAL_AUTHENTICATED_RID, 'test_role_one', 'test_role_two'), $user->getRoles());
$user->removeRole('test_role_three'); $user->removeRole('test_role_three');
$this->assertTrue($user->hasRole('test_role_one')); $this->assertTrue($user->hasRole('test_role_one'));
$this->assertTrue($user->hasRole('test_role_two')); $this->assertTrue($user->hasRole('test_role_two'));
$this->assertEqual(array('test_role_one', 'test_role_two'), $user->getRoles()); $this->assertEqual(array(DRUPAL_AUTHENTICATED_RID, 'test_role_one', 'test_role_two'), $user->getRoles());
$user->removeRole('test_role_one'); $user->removeRole('test_role_one');
$this->assertFalse($user->hasRole('test_role_one')); $this->assertFalse($user->hasRole('test_role_one'));
$this->assertTrue($user->hasRole('test_role_two')); $this->assertTrue($user->hasRole('test_role_two'));
$this->assertEqual(array('test_role_two'), $user->getRoles()); $this->assertEqual(array(DRUPAL_AUTHENTICATED_RID, 'test_role_two'), $user->getRoles());
} }
} }
...@@ -88,10 +88,10 @@ function testCreateUserWithRole() { ...@@ -88,10 +88,10 @@ function testCreateUserWithRole() {
private function userLoadAndCheckRoleAssigned($account, $rid, $is_assigned = TRUE) { private function userLoadAndCheckRoleAssigned($account, $rid, $is_assigned = TRUE) {