Update to Drupal 8.0.0 beta 14. For more information, see https://drupal.org/node/2544542

This commit is contained in:
Pantheon Automation 2015-08-27 12:03:05 -07:00 committed by Greg Anderson
parent 3b2511d96d
commit 81ccda77eb
2155 changed files with 54307 additions and 46870 deletions

View file

@ -0,0 +1,78 @@
<?php
/**
* @file
* Contains \Drupal\user\ContextProvider\CurrentUserContext.
*/
namespace Drupal\user\ContextProvider;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Plugin\Context\Context;
use Drupal\Core\Plugin\Context\ContextDefinition;
use Drupal\Core\Plugin\Context\ContextProviderInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
/**
* Sets the current user as a context.
*/
class CurrentUserContext implements ContextProviderInterface {
use StringTranslationTrait;
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $account;
/**
* The user storage.
*
* @var \Drupal\user\UserStorageInterface
*/
protected $userStorage;
/**
* Constructs a new CurrentUserContext.
*
* @param \Drupal\Core\Session\AccountInterface $account
* The current user.
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
*/
public function __construct(AccountInterface $account, EntityManagerInterface $entity_manager) {
$this->account = $account;
$this->userStorage = $entity_manager->getStorage('user');
}
/**
* {@inheritdoc}
*/
public function getRuntimeContexts(array $unqualified_context_ids) {
$current_user = $this->userStorage->load($this->account->id());
$context = new Context(new ContextDefinition('entity:user', $this->t('Current user')));
$context->setContextValue($current_user);
$cacheability = new CacheableMetadata();
$cacheability->setCacheContexts(['user']);
$context->addCacheableDependency($cacheability);
$result = [
'current_user' => $context,
];
return $result;
}
/**
* {@inheritdoc}
*/
public function getAvailableContexts() {
return $this->getRuntimeContexts([]);
}
}

View file

@ -7,6 +7,7 @@
namespace Drupal\user\Controller;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Datetime\DateFormatter;
@ -123,7 +124,7 @@ class UserController extends ControllerBase {
drupal_set_message($this->t('You have tried to use a one-time login link that has expired. Please request a new one using the form below.'), 'error');
return $this->redirect('user.pass');
}
elseif ($user->isAuthenticated() && ($timestamp >= $user->getLastLoginTime()) && ($timestamp <= $current) && ($hash === user_pass_rehash($user->getPassword(), $timestamp, $user->getLastLoginTime(), $user->id()))) {
elseif ($user->isAuthenticated() && ($timestamp >= $user->getLastLoginTime()) && ($timestamp <= $current) && ($hash === user_pass_rehash($user, $timestamp))) {
$expiration_date = $user->getLastLoginTime() ? $this->dateFormatter->format($timestamp + $timeout) : NULL;
return $this->formBuilder()->getForm('Drupal\user\Form\UserPasswordResetForm', $user, $expiration_date, $timestamp, $hash);
}
@ -161,7 +162,7 @@ class UserController extends ControllerBase {
* The user account name.
*/
public function userTitle(UserInterface $user = NULL) {
return $user ? Xss::filter($user->getUsername()) : '';
return $user ? SafeMarkup::xssFilter($user->getUsername()) : '';
}
/**
@ -197,7 +198,7 @@ class UserController extends ControllerBase {
$account_data = $this->userData->get('user', $user->id());
if (isset($account_data['cancel_method']) && !empty($timestamp) && !empty($hashed_pass)) {
// Validate expiration and hashed password/login.
if ($timestamp <= $current && $current - $timestamp < $timeout && $user->id() && $timestamp >= $user->getLastLoginTime() && $hashed_pass == user_pass_rehash($user->getPassword(), $timestamp, $user->getLastLoginTime(), $user->id())) {
if ($timestamp <= $current && $current - $timestamp < $timeout && $user->id() && $timestamp >= $user->getLastLoginTime() && $hashed_pass == user_pass_rehash($user, $timestamp)) {
$edit = array(
'user_cancel_notify' => isset($account_data['cancel_notify']) ? $account_data['cancel_notify'] : $this->config('user.settings')->get('notify.status_canceled'),
);

View file

@ -79,7 +79,7 @@ class UserLoginBlock extends BlockBase implements ContainerFactoryPluginInterfac
$route_name = $this->routeMatch->getRouteName();
if ($account->isAnonymous() && !in_array($route_name, array('user.register', 'user.login', 'user.logout'))) {
return AccessResult::allowed()
->addCacheContexts(['route', 'user.roles:anonymous']);
->addCacheContexts(['route.name', 'user.roles:anonymous']);
}
return AccessResult::forbidden();
}

View file

@ -87,4 +87,17 @@ class UserRole extends ConditionPluginBase {
return (bool) array_intersect($this->configuration['roles'], $user->getRoles());
}
/**
* {@inheritdoc}
*/
public function getCacheContexts() {
// Optimize cache context, if a user cache context is provided, only use
// user.roles, since that's the only part this condition cares about.
$contexts = [];
foreach (parent::getCacheContexts() as $context) {
$contexts[] = $context == 'user' ? 'user.roles' : $context;
}
return $contexts;
}
}

View file

@ -50,7 +50,7 @@ class RolesRid extends ManyToOne {
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return parent::create($container, $configuration, $plugin_id, $plugin_definition, $container->get('entity.manager'));
return new static($configuration, $plugin_id, $plugin_definition, $container->get('entity.manager'));
}
/**

View file

@ -7,7 +7,6 @@
namespace Drupal\user;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface;
use Drupal\Core\Lock\LockBackendInterface;
use Drupal\Core\Session\AccountProxyInterface;
@ -122,10 +121,7 @@ class PrivateTempStore {
if (!$this->lockBackend->acquire($key)) {
$this->lockBackend->wait($key);
if (!$this->lockBackend->acquire($key)) {
throw new TempStoreException(SafeMarkup::format("Couldn't acquire lock to update item %key in %collection temporary storage.", array(
'%key' => $key,
'%collection' => $this->storage->getCollectionName(),
)));
throw new TempStoreException("Couldn't acquire lock to update item '$key' in '{$this->storage->getCollectionName()}' temporary storage.");
}
}
@ -180,10 +176,7 @@ class PrivateTempStore {
if (!$this->lockBackend->acquire($key)) {
$this->lockBackend->wait($key);
if (!$this->lockBackend->acquire($key)) {
throw new TempStoreException(SafeMarkup::format("Couldn't acquire lock to delete item %key from %collection temporary storage.", array(
'%key' => $key,
'%collection' => $this->storage->getCollectionName(),
)));
throw new TempStoreException("Couldn't acquire lock to delete item '$key' from '{$this->storage->getCollectionName()}' temporary storage.");
}
}
$this->storage->delete($key);

View file

@ -78,9 +78,9 @@ interface RoleInterface extends ConfigEntityInterface {
* Sets the role to be an admin role.
*
* @param bool $is_admin
* TRUE, if the role should be an admin role.
* TRUE if the role should be an admin role.
*
* return $this
* @return $this
*/
public function setIsAdmin($is_admin);

View file

@ -7,7 +7,6 @@
namespace Drupal\user;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface;
use Drupal\Core\Lock\LockBackendInterface;
use Symfony\Component\HttpFoundation\RequestStack;
@ -196,10 +195,7 @@ class SharedTempStore {
if (!$this->lockBackend->acquire($key)) {
$this->lockBackend->wait($key);
if (!$this->lockBackend->acquire($key)) {
throw new TempStoreException(SafeMarkup::format("Couldn't acquire lock to update item %key in %collection temporary storage.", array(
'%key' => $key,
'%collection' => $this->storage->getCollectionName(),
)));
throw new TempStoreException("Couldn't acquire lock to update item '$key' in '{$this->storage->getCollectionName()}' temporary storage.");
}
}
@ -242,10 +238,7 @@ class SharedTempStore {
if (!$this->lockBackend->acquire($key)) {
$this->lockBackend->wait($key);
if (!$this->lockBackend->acquire($key)) {
throw new TempStoreException(SafeMarkup::format("Couldn't acquire lock to delete item %key from %collection temporary storage.", array(
'%key' => $key,
'%collection' => $this->storage->getCollectionName(),
)));
throw new TempStoreException("Couldn't acquire lock to delete item '$key' from {$this->storage->getCollectionName()} temporary storage.");
}
}
$this->storage->delete($key);

View file

@ -59,7 +59,7 @@ class UserCancelTest extends WebTestBase {
// Attempt bogus account cancellation request confirmation.
$timestamp = $account->getLastLoginTime();
$this->drupalGet("user/" . $account->id() . "/cancel/confirm/$timestamp/" . user_pass_rehash($account->getPassword(), $timestamp, $account->getLastLoginTime(), $account->id()));
$this->drupalGet("user/" . $account->id() . "/cancel/confirm/$timestamp/" . user_pass_rehash($account, $timestamp));
$this->assertResponse(403, 'Bogus cancelling request rejected.');
$user_storage->resetCache(array($account->id()));
$account = $user_storage->load($account->id());
@ -165,7 +165,7 @@ class UserCancelTest extends WebTestBase {
// Attempt bogus account cancellation request confirmation.
$bogus_timestamp = $timestamp + 60;
$this->drupalGet("user/" . $account->id() . "/cancel/confirm/$bogus_timestamp/" . user_pass_rehash($account->getPassword(), $bogus_timestamp, $account->getLastLoginTime(), $account->id()));
$this->drupalGet("user/" . $account->id() . "/cancel/confirm/$bogus_timestamp/" . user_pass_rehash($account, $bogus_timestamp));
$this->assertText(t('You have tried to use an account cancellation link that has expired. Please request a new one using the form below.'), 'Bogus cancelling request rejected.');
$user_storage->resetCache(array($account->id()));
$account = $user_storage->load($account->id());
@ -173,7 +173,7 @@ class UserCancelTest extends WebTestBase {
// Attempt expired account cancellation request confirmation.
$bogus_timestamp = $timestamp - 86400 - 60;
$this->drupalGet("user/" . $account->id() . "/cancel/confirm/$bogus_timestamp/" . user_pass_rehash($account->getPassword(), $bogus_timestamp, $account->getLastLoginTime(), $account->id()));
$this->drupalGet("user/" . $account->id() . "/cancel/confirm/$bogus_timestamp/" . user_pass_rehash($account, $bogus_timestamp));
$this->assertText(t('You have tried to use an account cancellation link that has expired. Please request a new one using the form below.'), 'Expired cancel account request rejected.');
$user_storage->resetCache(array($account->id()));
$account = $user_storage->load($account->id());
@ -214,7 +214,7 @@ class UserCancelTest extends WebTestBase {
$this->assertText(t('A confirmation request to cancel your account has been sent to your email address.'), 'Account cancellation request mailed message displayed.');
// Confirm account cancellation request.
$this->drupalGet("user/" . $account->id() . "/cancel/confirm/$timestamp/" . user_pass_rehash($account->getPassword(), $timestamp, $account->getLastLoginTime(), $account->id()));
$this->drupalGet("user/" . $account->id() . "/cancel/confirm/$timestamp/" . user_pass_rehash($account, $timestamp));
$user_storage->resetCache(array($account->id()));
$account = $user_storage->load($account->id());
$this->assertTrue($account->isBlocked(), 'User has been blocked.');
@ -272,7 +272,7 @@ class UserCancelTest extends WebTestBase {
$this->assertText(t('A confirmation request to cancel your account has been sent to your email address.'), 'Account cancellation request mailed message displayed.');
// Confirm account cancellation request.
$this->drupalGet("user/" . $account->id() . "/cancel/confirm/$timestamp/" . user_pass_rehash($account->getPassword(), $timestamp, $account->getLastLoginTime(), $account->id()));
$this->drupalGet("user/" . $account->id() . "/cancel/confirm/$timestamp/" . user_pass_rehash($account, $timestamp));
$user_storage->resetCache(array($account->id()));
$account = $user_storage->load($account->id());
$this->assertTrue($account->isBlocked(), 'User has been blocked.');
@ -348,7 +348,7 @@ class UserCancelTest extends WebTestBase {
$this->assertText(t('A confirmation request to cancel your account has been sent to your email address.'), 'Account cancellation request mailed message displayed.');
// Confirm account cancellation request.
$this->drupalGet("user/" . $account->id() . "/cancel/confirm/$timestamp/" . user_pass_rehash($account->getPassword(), $timestamp, $account->getLastLoginTime(), $account->id()));
$this->drupalGet("user/" . $account->id() . "/cancel/confirm/$timestamp/" . user_pass_rehash($account, $timestamp));
$user_storage->resetCache(array($account->id()));
$this->assertFalse($user_storage->load($account->id()), 'User is not found in the database.');
@ -427,7 +427,7 @@ class UserCancelTest extends WebTestBase {
$this->assertText(t('A confirmation request to cancel your account has been sent to your email address.'), 'Account cancellation request mailed message displayed.');
// Confirm account cancellation request.
$this->drupalGet("user/" . $account->id() . "/cancel/confirm/$timestamp/" . user_pass_rehash($account->getPassword(), $timestamp, $account->getLastLoginTime(), $account->id()));
$this->drupalGet("user/" . $account->id() . "/cancel/confirm/$timestamp/" . user_pass_rehash($account, $timestamp));
$user_storage->resetCache(array($account->id()));
$this->assertFalse($user_storage->load($account->id()), 'User is not found in the database.');

View file

@ -144,14 +144,14 @@ class UserPasswordResetTest extends PageCacheTagsTestBase {
$timeout = $this->config('user.settings')->get('password_reset_timeout');
$bogus_timestamp = REQUEST_TIME - $timeout - 60;
$_uid = $this->account->id();
$this->drupalGet("user/reset/$_uid/$bogus_timestamp/" . user_pass_rehash($this->account->getPassword(), $bogus_timestamp, $this->account->getLastLoginTime(), $this->account->id()));
$this->drupalGet("user/reset/$_uid/$bogus_timestamp/" . user_pass_rehash($this->account, $bogus_timestamp));
$this->assertText(t('You have tried to use a one-time login link that has expired. Please request a new one using the form below.'), 'Expired password reset request rejected.');
// Create a user, block the account, and verify that a login link is denied.
$timestamp = REQUEST_TIME - 1;
$blocked_account = $this->drupalCreateUser()->block();
$blocked_account->save();
$this->drupalGet("user/reset/" . $blocked_account->id() . "/$timestamp/" . user_pass_rehash($blocked_account->getPassword(), $timestamp, $blocked_account->getLastLoginTime(), $this->account->id()));
$this->drupalGet("user/reset/" . $blocked_account->id() . "/$timestamp/" . user_pass_rehash($blocked_account, $timestamp));
$this->assertResponse(403);
// Verify a blocked user can not request a new password.
@ -162,6 +162,16 @@ class UserPasswordResetTest extends PageCacheTagsTestBase {
$this->drupalPostForm(NULL, $edit, t('Submit'));
$this->assertRaw(t('%name is blocked or has not been activated yet.', array('%name' => $blocked_account->getUsername())), 'Notified user blocked accounts can not request a new password');
$this->assertTrue(count($this->drupalGetMails(array('id' => 'user_password_reset'))) === $before, 'No email was sent when requesting password reset for a blocked account');
// Verify a password reset link is invalidated when the user's email address changes.
$this->drupalGet('user/password');
$edit = array('name' => $this->account->getUsername());
$this->drupalPostForm(NULL, $edit, t('Submit'));
$old_email_reset_link = $this->getResetURL();
$this->account->setEmail("1" . $this->account->getEmail());
$this->account->save();
$this->drupalGet($old_email_reset_link);
$this->assertText(t('You have tried to use a one-time login link that has either been used or is no longer valid. Please request a new one using the form below.'), 'One-time link is no longer valid.');
}
/**

View file

@ -8,6 +8,7 @@
namespace Drupal\user\Tests;
use Drupal\simpletest\WebTestBase;
use Drupal\file\Entity\File;
/**
* Tests user picture functionality.
@ -75,7 +76,7 @@ class UserPictureTest extends WebTestBase {
\Drupal::service('cron')->run();
// Verify that the image has been deleted.
$this->assertFalse(file_load($file->id(), TRUE), 'File was removed from the database.');
$this->assertFalse(File::load($file->id()), 'File was removed from the database.');
// Clear out PHP's file stat cache so we see the current value.
clearstatcache(TRUE, $file->getFileUri());
$this->assertFalse(is_file($file->getFileUri()), 'File was removed from the file system.');
@ -133,6 +134,6 @@ class UserPictureTest extends WebTestBase {
$user_storage = $this->container->get('entity.manager')->getStorage('user');
$user_storage->resetCache(array($this->webUser->id()));
$account = $user_storage->load($this->webUser->id());
return file_load($account->user_picture->target_id, TRUE);
return File::load($account->user_picture->target_id);
}
}

View file

@ -8,6 +8,7 @@
namespace Drupal\user\Tests;
use Drupal\Core\Entity\Entity\EntityFormDisplay;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
@ -264,6 +265,24 @@ class UserRegistrationTest extends WebTestBase {
$this->assertEqual($new_user->init->value, $mail, 'Correct init field.');
}
/**
* Tests username and email field constraints on user registration.
*
* @see \Drupal\user\Plugin\Validation\Constraint\UserNameUnique
* @see \Drupal\user\Plugin\Validation\Constraint\UserMailUnique
*/
public function testUniqueFields() {
$account = $this->drupalCreateUser();
$edit = ['mail' => 'test@example.com', 'name' => $account->getUsername()];
$this->drupalPostForm('user/register', $edit, t('Create new account'));
$this->assertRaw(SafeMarkup::format('The username %value is already taken.', ['%value' => $account->getUsername()]));
$edit = ['mail' => $account->getEmail(), 'name' => $this->randomString()];
$this->drupalPostForm('user/register', $edit, t('Create new account'));
$this->assertRaw(SafeMarkup::format('The email address %value is already taken.', ['%value' => $account->getEmail()]));
}
/**
* Tests Field API fields on user registration forms.
*/

View file

@ -8,6 +8,7 @@
namespace Drupal\user\Tests;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\simpletest\WebTestBase;
use Drupal\user\Entity\User;
@ -66,15 +67,58 @@ class UserTokenReplaceTest extends WebTestBase {
$tests['[user:created:short]'] = format_date($account->getCreatedTime(), 'short', '', NULL, $language_interface->getId());
$tests['[current-user:name]'] = SafeMarkup::checkPlain(user_format_name($global_account));
$base_bubbleable_metadata = BubbleableMetadata::createFromObject($account);
$metadata_tests = [];
$metadata_tests['[user:uid]'] = $base_bubbleable_metadata;
$metadata_tests['[user:name]'] = $base_bubbleable_metadata;
$metadata_tests['[user:mail]'] = $base_bubbleable_metadata;
$metadata_tests['[user:url]'] = $base_bubbleable_metadata;
$metadata_tests['[user:edit-url]'] = $base_bubbleable_metadata;
$bubbleable_metadata = clone $base_bubbleable_metadata;
// This test runs with the Language module enabled, which means config is
// overridden by LanguageConfigFactoryOverride (to provide translations of
// config). This causes the interface language cache context to be added for
// config entities. The four next tokens use DateFormat Config entities, and
// therefore have the interface language cache context.
$bubbleable_metadata->addCacheContexts(['languages:language_interface']);
$metadata_tests['[user:last-login]'] = $bubbleable_metadata->addCacheTags(['rendered']);
$metadata_tests['[user:last-login:short]'] = $bubbleable_metadata;
$metadata_tests['[user:created]'] = $bubbleable_metadata;
$metadata_tests['[user:created:short]'] = $bubbleable_metadata;
$metadata_tests['[current-user:name]'] = $base_bubbleable_metadata->merge(BubbleableMetadata::createFromObject($global_account)->addCacheContexts(['user']));
// Test to make sure that we generated something for each token.
$this->assertFalse(in_array(0, array_map('strlen', $tests)), 'No empty tokens generated.');
foreach ($tests as $input => $expected) {
$output = $token_service->replace($input, array('user' => $account), array('langcode' => $language_interface->getId()));
$bubbleable_metadata = new BubbleableMetadata();
$output = $token_service->replace($input, array('user' => $account), array('langcode' => $language_interface->getId()), $bubbleable_metadata);
$this->assertEqual($output, $expected, format_string('Sanitized user token %token replaced.', array('%token' => $input)));
$this->assertEqual($bubbleable_metadata, $metadata_tests[$input]);
}
// Generate tokens for the anonymous user.
$anonymous_user = User::load(0);
$tests = [];
$tests['[user:uid]'] = t('not yet assigned');
$tests['[user:name]'] = SafeMarkup::checkPlain(user_format_name($anonymous_user));
$base_bubbleable_metadata = BubbleableMetadata::createFromObject($anonymous_user);
$metadata_tests = [];
$metadata_tests['[user:uid]'] = $base_bubbleable_metadata;
$bubbleable_metadata = clone $base_bubbleable_metadata;
$bubbleable_metadata->addCacheableDependency(\Drupal::config('user.settings'));
$metadata_tests['[user:name]'] = $bubbleable_metadata;
foreach ($tests as $input => $expected) {
$bubbleable_metadata = new BubbleableMetadata();
$output = $token_service->replace($input, array('user' => $anonymous_user), array('langcode' => $language_interface->getId()), $bubbleable_metadata);
$this->assertEqual($output, $expected, format_string('Sanitized user token %token replaced.', array('%token' => $input)));
$this->assertEqual($bubbleable_metadata, $metadata_tests[$input]);
}
// Generate and test unsanitized tokens.
$tests = [];
$tests['[user:name]'] = user_format_name($account);
$tests['[user:mail]'] = $account->getEmail();
$tests['[current-user:name]'] = user_format_name($global_account);

View file

@ -7,6 +7,7 @@
namespace Drupal\user\Tests\Views;
use Drupal\Core\Render\RenderContext;
use Drupal\views\Views;
/**
@ -25,6 +26,9 @@ class HandlerFieldUserNameTest extends UserTestBase {
public static $testViews = array('test_views_handler_field_user_name');
public function testUserName() {
/** @var \Drupal\Core\Render\RendererInterface $renderer */
$renderer = \Drupal::service('renderer');
$this->drupalLogin($this->drupalCreateUser(array('access user profiles')));
// Set defaults.
@ -37,13 +41,17 @@ class HandlerFieldUserNameTest extends UserTestBase {
$this->executeView($view);
$anon_name = $this->config('user.settings')->get('anonymous');
$render = $view->field['name']->advancedRender($view->result[0]);
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
return $view->field['name']->advancedRender($view->result[0]);
});
$this->assertTrue(strpos($render, $anon_name) !== FALSE, 'For user 0 it should use the default anonymous name by default.');
$username = $this->randomMachineName();
$view->result[0]->_entity->setUsername($username);
$view->result[0]->_entity->uid->value = 1;
$render = $view->field['name']->advancedRender($view->result[0]);
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
return $view->field['name']->advancedRender($view->result[0]);
});
$this->assertTrue(strpos($render, $username) !== FALSE, 'If link to user is checked the username should be part of the output.');
$this->assertTrue(strpos($render, 'user/1') !== FALSE, 'If link to user is checked the link to the user should appear as well.');
@ -52,7 +60,9 @@ class HandlerFieldUserNameTest extends UserTestBase {
$username = $this->randomMachineName();
$view->result[0]->_entity->setUsername($username);
$view->result[0]->_entity->uid->value = 1;
$render = $view->field['name']->advancedRender($view->result[0]);
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
return $view->field['name']->advancedRender($view->result[0]);
});
$this->assertIdentical($render, $username, 'If the user is not linked the username should be printed out for a normal user.');
}
@ -61,13 +71,18 @@ class HandlerFieldUserNameTest extends UserTestBase {
* Tests that the field handler works when no additional fields are added.
*/
public function testNoAdditionalFields() {
/** @var \Drupal\Core\Render\RendererInterface $renderer */
$renderer = \Drupal::service('renderer');
$view = Views::getView('test_views_handler_field_user_name');
$this->executeView($view);
$username = $this->randomMachineName();
$view->result[0]->_entity->setUsername($username);
$view->result[0]->_entity->uid->value = 1;
$render = $view->field['name']->advancedRender($view->result[0]);
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
return $view->field['name']->advancedRender($view->result[0]);
});
$this->assertTrue(strpos($render, $username) !== FALSE, 'If link to user is checked the username should be part of the output.');
}

View file

@ -80,8 +80,11 @@ class UserAccessControlHandler extends EntityAccessControlHandler {
$is_own_account = $items ? $items->getEntity()->id() == $account->id() : FALSE;
switch ($field_definition->getName()) {
case 'name':
// Allow view access to anyone with access to the entity.
if ($operation == 'view') {
// Allow view access to anyone with access to the entity. Anonymous
// users should be able to access the username field during the
// registration process, otherwise the username and email constraints
// are not checked.
if ($operation == 'view' || ($items && $account->isAnonymous() && $items->getEntity()->isAnonymous())) {
return AccessResult::allowed()->cachePerPermissions();
}
// Allow edit access for the own user name if the permission is

View file

@ -9,7 +9,7 @@ namespace Drupal\user;
use Drupal\Core\Database\Connection;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
@ -72,14 +72,14 @@ class UserStorage extends SqlContentEntityStorage implements UserStorageInterfac
/**
* {@inheritdoc}
*/
public function save(EntityInterface $entity) {
protected function doSaveFieldItems(ContentEntityInterface $entity, array $names = []) {
// The anonymous user account is saved with the fixed user ID of 0.
// Therefore we need to check for NULL explicitly.
if ($entity->id() === NULL) {
$entity->uid->value = $this->database->nextId($this->database->query('SELECT MAX(uid) FROM {users}')->fetchField());
$entity->enforceIsNew();
}
return parent::save($entity);
return parent::doSaveFieldItems($entity, $names);
}
/**

View file

@ -8,14 +8,8 @@
*
* Available variables:
* - content: A list of content items. Use 'content' to print all content, or
* print a subset such as 'content.field_example'.
* - Field variables: For each field attached to the user a corresponding
* variable is defined; e.g., account.field_example has a variable
* 'field_example' defined. When needing to access a field's raw values,
* developers/themers are strongly encouraged to use these variables.
* Otherwise they will have to explicitly specify the desired field language,
* e.g. account.field_example.en, thus overriding any language negotiation
* rule that was previously applied.
* print a subset such as 'content.field_example'. Fields attached to a user
* such as 'user_picture' are available as 'content.user_picture'.
* - attributes: HTML attributes for the container element.
* - user: A Drupal User entity.
*

View file

@ -168,35 +168,41 @@ class PermissionHandlerTest extends UnitTestCase {
->method('getModuleDirectories')
->willReturn([
'module_a' => vfsStream::url('modules/module_a'),
'module_b' => vfsStream::url('modules/module_b'),
'module_c' => vfsStream::url('modules/module_c'),
]);
$this->moduleHandler->expects($this->exactly(3))
->method('getName')
->will($this->returnValueMap([
['module_a', 'Module a'],
['module_b', 'Module b'],
['module_c', 'A Module'],
]));
$url = vfsStream::url('modules');
mkdir($url . '/module_a');
file_put_contents($url . '/module_a/module_a.permissions.yml',
"access_module_a2: single_description
access_module_a1: single_description"
"access_module_a2: single_description2
access_module_a1: single_description1"
);
mkdir($url . '/module_b');
file_put_contents($url . '/module_b/module_b.permissions.yml',
"access_module_a3: single_description"
);
mkdir($url . '/module_c');
file_put_contents($url . '/module_c/module_c.permissions.yml',
"access_module_a4: single_description"
);
$modules = ['module_a'];
$extensions = [
'module_a' => $this->mockModuleExtension('module_a', 'Module a'),
];
$this->moduleHandler->expects($this->any())
->method('getImplementations')
->with('permission')
->willReturn([]);
$this->moduleHandler->expects($this->any())
$modules = ['module_a', 'module_b', 'module_c'];
$this->moduleHandler->expects($this->once())
->method('getModuleList')
->willReturn(array_flip($modules));
$this->permissionHandler = new TestPermissionHandler($this->moduleHandler, $this->stringTranslation, $this->controllerResolver);
// Setup system_rebuild_module_data().
$this->permissionHandler->setSystemRebuildModuleData($extensions);
$actual_permissions = $this->permissionHandler->getPermissions();
$this->assertEquals(['access_module_a1', 'access_module_a2'], array_keys($actual_permissions));
$permissionHandler = new TestPermissionHandler($this->moduleHandler, $this->stringTranslation, $this->controllerResolver);
$actual_permissions = $permissionHandler->getPermissions();
$this->assertEquals(['access_module_a4', 'access_module_a1', 'access_module_a2', 'access_module_a3'],
array_keys($actual_permissions));
}
/**

View file

@ -8,14 +8,8 @@
*
* Available variables:
* - content: A list of content items. Use 'content' to print all content, or
* print a subset such as 'content.field_example'.
* - Field variables: For each field attached to the user a corresponding
* variable is defined; e.g., account.field_example has a variable
* 'field_example' defined. When needing to access a field's raw values,
* developers/themers are strongly encouraged to use these variables.
* Otherwise they will have to explicitly specify the desired field language,
* e.g. account.field_example.en, thus overriding any language negotiation
* rule that was previously applied.
* print a subset such as 'content.field_example'. Fields attached to a user
* such as 'user_picture' are available as 'content.user_picture'.
* - attributes: HTML attributes for the container element.
* - user: A Drupal User entity.
*

View file

@ -417,12 +417,6 @@ function user_format_name(AccountInterface $account) {
function user_template_preprocess_default_variables_alter(&$variables) {
$user = \Drupal::currentUser();
// If this function is called from the installer after Drupal has been
// installed then $user will not be set.
if (!is_object($user)) {
return;
}
$variables['user'] = clone $user;
// Remove password and session IDs, $form_state, since themes should not need nor see them.
unset($variables['user']->pass, $variables['user']->sid, $variables['user']->ssid);
@ -574,7 +568,7 @@ function user_pass_reset_url($account, $options = array()) {
array(
'uid' => $account->id(),
'timestamp' => $timestamp,
'hash' => user_pass_rehash($account->getPassword(), $timestamp, $account->getLastLoginTime(), $account->id()),
'hash' => user_pass_rehash($account, $timestamp),
),
array(
'absolute' => TRUE,
@ -587,11 +581,7 @@ function user_pass_reset_url($account, $options = array()) {
* Generates a URL to confirm an account cancellation request.
*
* @param \Drupal\user\UserInterface $account
* The user account object, which must contain at least the following
* properties:
* - uid: The user ID number.
* - pass: The hashed user password string.
* - login: The UNIX timestamp of the user's last login.
* The user account object.
* @param array $options
* (optional) A keyed array of settings. Supported options are:
* - langcode: A language code to be used when generating locale-sensitive
@ -604,14 +594,14 @@ function user_pass_reset_url($account, $options = array()) {
* @see user_mail_tokens()
* @see \Drupal\user\Controller\UserController::confirmCancel()
*/
function user_cancel_url($account, $options = array()) {
function user_cancel_url(UserInterface $account, $options = array()) {
$timestamp = REQUEST_TIME;
$langcode = isset($options['langcode']) ? $options['langcode'] : $account->getPreferredLangcode();
$url_options = array('absolute' => TRUE, 'language' => \Drupal::languageManager()->getLanguage($langcode));
return \Drupal::url('user.cancel_confirm', [
'user' => $account->id(),
'timestamp' => $timestamp,
'hashed_pass' => user_pass_rehash($account->getPassword(), $timestamp, $account->getLastLoginTime(), $account->id())
'hashed_pass' => user_pass_rehash($account, $timestamp)
], $url_options);
}
@ -621,26 +611,26 @@ function user_cancel_url($account, $options = array()) {
* This hash is normally used to build a unique and secure URL that is sent to
* the user by email for purposes such as resetting the user's password. In
* order to validate the URL, the same hash can be generated again, from the
* same information, and compared to the hash value from the URL. The URL
* normally contains both the time stamp and the numeric user ID. The login
* timestamp and hashed password are retrieved from the database as necessary.
* same information, and compared to the hash value from the URL. The hash
* contains the time stamp, the user's last login time, the numeric user ID,
* and the user's email address.
* For a usage example, see user_cancel_url() and
* \Drupal\user\Controller\UserController::confirmCancel().
*
* @param string $password
* The hashed user account password value.
* @param \Drupal\user\UserInterface $account
* An object containing the user account.
* @param int $timestamp
* A UNIX timestamp, typically REQUEST_TIME.
* @param int $login
* The UNIX timestamp of the user's last login.
* @param int $uid
* The user ID.
*
* @return string
* A string that is safe for use in URLs and SQL statements.
*/
function user_pass_rehash($password, $timestamp, $login, $uid) {
return Crypt::hmacBase64($timestamp . $login . $uid, Settings::getHashSalt() . $password);
function user_pass_rehash(UserInterface $account, $timestamp) {
$data = $timestamp;
$data .= $account->getLastLoginTime();
$data .= $account->id();
$data .= $account->getEmail();
return Crypt::hmacBase64($data, Settings::getHashSalt() . $account->getPassword());
}
/**

View file

@ -61,6 +61,11 @@ services:
user.permissions:
class: Drupal\user\PermissionHandler
arguments: ['@module_handler', '@string_translation', '@controller_resolver']
user.current_user_context:
class: Drupal\user\ContextProvider\CurrentUserContext
arguments: ['@current_user', '@entity.manager']
tags:
- { name: 'context_provider' }
parameters:
user.tempstore.expire: 604800

View file

@ -6,6 +6,8 @@
*/
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Datetime\Entity\DateFormat;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\user\Entity\User;
/**
@ -64,7 +66,7 @@ function user_token_info() {
/**
* Implements hook_tokens().
*/
function user_tokens($type, $tokens, array $data = array(), array $options = array()) {
function user_tokens($type, $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata) {
$token_service = \Drupal::token();
$url_options = array('absolute' => TRUE);
@ -80,6 +82,7 @@ function user_tokens($type, $tokens, array $data = array(), array $options = arr
$replacements = array();
if ($type == 'user' && !empty($data['user'])) {
/** @var \Drupal\user\UserInterface $account */
$account = $data['user'];
foreach ($tokens as $name => $original) {
switch ($name) {
@ -91,6 +94,9 @@ function user_tokens($type, $tokens, array $data = array(), array $options = arr
case 'name':
$name = user_format_name($account);
if ($account->isAnonymous()) {
$bubbleable_metadata->addCacheableDependency(\Drupal::config('user.settings'));
}
$replacements[$original] = $sanitize ? SafeMarkup::checkPlain($name) : $name;
break;
@ -108,10 +114,14 @@ function user_tokens($type, $tokens, array $data = array(), array $options = arr
// These tokens are default variations on the chained tokens handled below.
case 'last-login':
$date_format = DateFormat::load('medium');
$bubbleable_metadata->addCacheableDependency($date_format);
$replacements[$original] = $account->getLastLoginTime() ? format_date($account->getLastLoginTime(), 'medium', '', NULL, $langcode) : t('never');
break;
case 'created':
$date_format = DateFormat::load('medium');
$bubbleable_metadata->addCacheableDependency($date_format);
// In the case of user_presave the created date may not yet be set.
$replacements[$original] = $account->getCreatedTime() ? format_date($account->getCreatedTime(), 'medium', '', NULL, $langcode) : t('not yet created');
break;
@ -119,17 +129,18 @@ function user_tokens($type, $tokens, array $data = array(), array $options = arr
}
if ($login_tokens = $token_service->findWithPrefix($tokens, 'last-login')) {
$replacements += $token_service->generate('date', $login_tokens, array('date' => $account->getLastLoginTime()), $options);
$replacements += $token_service->generate('date', $login_tokens, array('date' => $account->getLastLoginTime()), $options, $bubbleable_metadata);
}
if ($registered_tokens = $token_service->findWithPrefix($tokens, 'created')) {
$replacements += $token_service->generate('date', $registered_tokens, array('date' => $account->getCreatedTime()), $options);
$replacements += $token_service->generate('date', $registered_tokens, array('date' => $account->getCreatedTime()), $options, $bubbleable_metadata);
}
}
if ($type == 'current-user') {
$account = User::load(\Drupal::currentUser()->id());
$replacements += $token_service->generate('user', $tokens, array('user' => $account), $options);
$bubbleable_metadata->addCacheContexts(['user']);
$replacements += $token_service->generate('user', $tokens, array('user' => $account), $options, $bubbleable_metadata);
}
return $replacements;