Commit 4d0d5c57 authored by catch's avatar catch

Issue #347988 by sun, Berdir, dawehner, Damien Tournoud, moshe weitzman: Move...

Issue #347988 by sun, Berdir, dawehner, Damien Tournoud, moshe weitzman: Move $user->data into own table.
parent 9e28980b
......@@ -105,9 +105,6 @@ function _drupal_session_read($sid) {
// We found the client's session record and they are an authenticated,
// active user.
if ($user && $user->uid > 0 && $user->status == 1) {
// This is done to unserialize the data member of $user.
$user->data = unserialize($user->data);
// Add roles element to $user.
$user->roles = array();
$user->roles[DRUPAL_AUTHENTICATED_RID] = DRUPAL_AUTHENTICATED_RID;
......
......@@ -233,6 +233,10 @@ function block_update_dependencies() {
$dependencies['block'][8002] = array(
'user' => 8002,
);
// Migrate users.data after User module prepared the tables.
$dependencies['block'][8005] = array(
'user' => 8011,
);
return $dependencies;
}
......@@ -353,6 +357,28 @@ function block_update_8004() {
}
}
/**
* Migrate {users}.data into {users_data}.
*/
function block_update_8005() {
$query = db_select('_d7_users_data', 'ud');
$query->addField('ud', 'uid');
$query->addExpression("'block'", 'module');
$query->addExpression("'block'", 'name');
// Take over the extracted and serialized value in {_d7_users_data} as-is.
$query->addField('ud', 'value');
$query->addExpression('1', 'serialized');
$query->condition('name', 'block');
db_insert('users_data')
->from($query)
->execute();
db_delete('_d7_users_data')
->condition('name', 'block')
->execute();
}
/**
* @} End of "addtogroup updates-7.x-to-8.x".
* The next series of updates should start at 9000.
......
......@@ -599,6 +599,7 @@ function block_form_user_profile_form_alter(&$form, &$form_state) {
$account = $form_state['controller']->getEntity($form_state);
$rids = array_keys($account->roles);
$result = db_query("SELECT DISTINCT b.* FROM {block} b LEFT JOIN {block_role} r ON b.module = r.module AND b.delta = r.delta WHERE b.status = 1 AND b.custom <> 0 AND (r.rid IN (:rids) OR r.rid IS NULL) ORDER BY b.weight, b.module", array(':rids' => $rids));
$account_data = drupal_container()->get('user.data')->get('block', $account->id(), 'block');
$blocks = array();
foreach ($result as $block) {
......@@ -607,7 +608,7 @@ function block_form_user_profile_form_alter(&$form, &$form_state) {
$blocks[$block->module][$block->delta] = array(
'#type' => 'checkbox',
'#title' => check_plain($data[$block->delta]['info']),
'#default_value' => isset($account->data['block'][$block->module][$block->delta]) ? $account->data['block'][$block->module][$block->delta] : ($block->custom == 1),
'#default_value' => isset($account_data[$block->module][$block->delta]) ? $account_data[$block->module][$block->delta] : ($block->custom == 1),
);
}
}
......@@ -639,11 +640,11 @@ function block_field_extra_fields() {
}
/**
* Implements hook_user_presave().
* Implements hook_user_update().
*/
function block_user_presave($account) {
function block_user_update($account) {
if (isset($account->block)) {
$account->data['block'] = $account->block;
drupal_container()->get('user.data')->set('block', $account->id(), 'block', $account->block);
}
}
......@@ -805,6 +806,10 @@ function block_block_list_alter(&$blocks) {
$block_langcodes[$record->module][$record->delta][$record->type][$record->langcode] = TRUE;
}
if ($user->uid) {
$user_data = drupal_container()->get('user.data')->get('block', $user->uid, 'block');
}
foreach ($blocks as $key => $block) {
if (!isset($block->theme) || !isset($block->status) || $block->theme != $theme_key || $block->status != 1) {
// This block was added by a contrib module, leave it in the list.
......@@ -822,8 +827,8 @@ function block_block_list_alter(&$blocks) {
// Use the user's block visibility setting, if necessary.
if ($block->custom != BLOCK_CUSTOM_FIXED) {
if ($user->uid && isset($user->data['block'][$block->module][$block->delta])) {
$enabled = $user->data['block'][$block->module][$block->delta];
if ($user->uid && isset($user_data[$block->module][$block->delta])) {
$enabled = $user_data[$block->module][$block->delta];
}
else {
$enabled = ($block->custom == BLOCK_CUSTOM_ENABLED);
......
......@@ -21,6 +21,17 @@ function contact_install() {
* @{
*/
/**
* Implements hook_update_dependencies().
*/
function contact_update_dependencies() {
// Migrate users.data after User module prepared the tables.
$dependencies['contact'][8003] = array(
'user' => 8011,
);
return $dependencies;
}
/**
* Moves contact setting from variable to config.
*
......@@ -68,6 +79,26 @@ function contact_update_8002() {
db_drop_table('contact');
}
/**
* Migrate {users}.data into {users_data}.
*/
function contact_update_8003() {
$query = db_select('_d7_users_data', 'ud');
$query->condition('name', 'contact');
$query->addField('ud', 'uid');
$query->addExpression("'contact'", 'module');
$query->addExpression("'enabled'", 'name');
$query->addField('ud', 'value', 'value');
$query->addExpression(1, 'serialized');
db_insert('users_data')
->from($query)
->execute();
db_delete('_d7_users_data')
->condition('name', 'contact')
->execute();
}
/**
* @} End of "defgroup updates-7.x-to-8.x".
* The next series of updates should start at 9000.
......
......@@ -140,14 +140,20 @@ function _contact_personal_tab_access($account) {
return TRUE;
}
// If the requested user has disabled their contact form, or this preference
// has not yet been saved, do not allow users to contact them.
if (empty($account->data['contact'])) {
// If requested user has been blocked, do not allow users to contact them.
if (empty($account->status)) {
return FALSE;
}
// If requested user has been blocked, do not allow users to contact them.
if (empty($account->status)) {
// If the requested user has disabled their contact form, do not allow users
// to contact them.
$account_data = drupal_container()->get('user.data')->get('contact', $account->id(), 'enabled');
if (isset($account_data) && empty($account_data)) {
return FALSE;
}
// If the requested user did not save a preference yet, deny access if the
// configured default is disabled.
elseif (!config('contact.settings')->get('user_default_enabled')) {
return FALSE;
}
......@@ -290,19 +296,22 @@ function contact_form_user_profile_form_alter(&$form, &$form_state) {
'#collapsible' => TRUE,
);
$account = $form_state['controller']->getEntity($form_state);
$account_data = drupal_container()->get('user.data')->get('contact', $account->id(), 'enabled');
$form['contact']['contact'] = array(
'#type' => 'checkbox',
'#title' => t('Personal contact form'),
'#default_value' => !empty($account->data['contact']) ? $account->data['contact'] : FALSE,
'#default_value' => isset($account_data) ? $account_data : config('contact.settings')->get('user_default_enabled'),
'#description' => t('Allow other users to contact you via a personal contact form which keeps your e-mail address hidden. Note that some privileged users such as site administrators are still able to contact you even if you choose to disable this feature.'),
);
}
/**
* Implements hook_user_presave().
* Implements hook_user_update().
*/
function contact_user_presave($account) {
$account->data['contact'] = isset($account->contact) ? $account->contact : config('contact.settings')->get('user_default_enabled');
function contact_user_update($account) {
if (isset($account->contact)) {
drupal_container()->get('user.data')->set('contact', $account->id(), 'enabled', (int) $account->contact);
}
}
/**
......
......@@ -77,7 +77,6 @@ function testRegisterUserWithEmailVerification() {
$this->assertEqual($user->mail, 'john@example.com', 'User was registered with right email address.');
$this->assertEqual($user->timezone, 'Europe/London', 'User was registered with right timezone.');
$this->assertEqual($user->preferred_langcode, 'pt', 'User was registered with right language.');
$this->assertFalse($user->data, 'No additional user info was saved.');
$this->submitLoginForm($identity);
$this->assertRaw(t('You must validate your email address for this account before logging in via OpenID.'));
......@@ -126,7 +125,6 @@ function testRegisterUserWithoutEmailVerification() {
$this->assertEqual($user->mail, 'john@example.com', 'User was registered with right email address.');
$this->assertEqual($user->timezone, 'Europe/London', 'User was registered with right timezone.');
$this->assertEqual($user->preferred_langcode, 'pt-br', 'User was registered with right language.');
$this->assertFalse($user->data, 'No additional user info was saved.');
$this->drupalLogout();
......@@ -171,7 +169,6 @@ function testRegisterUserWithInvalidSreg() {
$user = user_load_by_name('john');
$this->assertTrue($user, 'User was registered with right username.');
$this->assertEqual($user->preferred_langcode, language_default()->langcode, 'User language is site default.');
$this->assertFalse($user->data, 'No additional user info was saved.');
// Follow the one-time login that was sent in the welcome e-mail.
$this->drupalGet($reset_url);
......@@ -211,7 +208,6 @@ function testRegisterUserWithoutSreg() {
$user = user_load_by_name('john');
$this->assertTrue($user, 'User was registered with right username.');
$this->assertEqual($user->preferred_langcode, language_default()->langcode, 'User language is site default.');
$this->assertFalse($user->data, 'No additional user info was saved.');
// Follow the one-time login that was sent in the welcome e-mail.
$this->drupalGet($reset_url);
......
......@@ -17,3 +17,48 @@ function overlay_enable() {
$_SESSION['overlay_enable_redirect'] = 1;
}
}
/**
* Implements hook_update_dependencies().
*/
function overlay_update_dependencies() {
// Migrate users.data after User module prepared the tables.
$dependencies['overlay'][8000] = array(
'user' => 8011,
);
return $dependencies;
}
/**
* Migrate {users}.data into {users_data}.
*/
function overlay_update_8000() {
$query = db_select('_d7_users_data', 'ud');
$query->condition('name', 'overlay');
$query->addField('ud', 'uid');
$query->addExpression("'overlay'", 'module');
$query->addExpression("'enabled'", 'name');
$query->addField('ud', 'value', 'value');
$query->addExpression(1, 'serialized');
db_insert('users_data')
->from($query)
->execute();
// Migrate 'overlay_message_dismissed'.
$query = db_select('_d7_users_data', 'ud');
$query->condition('name', 'overlay_message_dismissed');
$query->addField('ud', 'uid');
$query->addExpression("'overlay'", 'module');
$query->addExpression("'message_dismissed'", 'name');
$query->addField('ud', 'value', 'value');
$query->addExpression(1, 'serialized');
db_insert('users_data')
->from($query)
->execute();
db_delete('_d7_users_data')
->condition('name', array('overlay', 'overlay_message_dismissed'))
->execute();
}
......@@ -87,6 +87,7 @@ function overlay_theme() {
function overlay_form_user_profile_form_alter(&$form, &$form_state) {
$account = $form_state['controller']->getEntity($form_state);
if (user_access('access overlay', $account)) {
$account_data = drupal_container()->get('user.data')->get('overlay', $account->id(), 'enabled');
$form['overlay_control'] = array(
'#type' => 'details',
'#title' => t('Administrative overlay'),
......@@ -97,17 +98,17 @@ function overlay_form_user_profile_form_alter(&$form, &$form_state) {
'#type' => 'checkbox',
'#title' => t('Use the overlay for administrative pages.'),
'#description' => t('Show administrative pages on top of the page you started from.'),
'#default_value' => isset($account->data['overlay']) ? $account->data['overlay'] : 1,
'#default_value' => isset($account_data) ? $account_data : 1,
);
}
}
/**
* Implements hook_user_presave().
* Implements hook_user_update().
*/
function overlay_user_presave($account) {
function overlay_user_update($account) {
if (isset($account->overlay)) {
$account->data['overlay'] = $account->overlay;
drupal_container()->get('user.data')->set('overlay', $account->id(), 'enabled', (int) $account->overlay);
}
}
......@@ -126,7 +127,8 @@ function overlay_init() {
// Only act if the user has access to the overlay and a mode was not already
// set. Other modules can also enable the overlay directly for other uses.
$use_overlay = !isset($user->data['overlay']) || $user->data['overlay'];
$user_data = drupal_container()->get('user.data')->get('overlay', $user->uid, 'enabled');
$use_overlay = !isset($user_data) || $user_data;
if (empty($mode) && user_access('access overlay') && $use_overlay) {
$current_path = current_path();
// After overlay is enabled on the modules page, redirect to
......@@ -354,9 +356,7 @@ function overlay_user_dismiss_message() {
throw new AccessDeniedHttpException();
}
$account = user_load($user->uid);
$account->data['overlay_message_dismissed'] = 1;
$account->save();
drupal_container()->get('user.data')->set('overlay', $user->uid, 'message_dismissed', 1);
drupal_set_message(t('The message has been dismissed. You can change your overlay settings at any time by visiting your profile page.'));
// Destination is normally given. Go to the user profile as a fallback.
drupal_goto('user/' . $user->uid . '/edit');
......@@ -378,7 +378,13 @@ function overlay_user_dismiss_message() {
function overlay_disable_message() {
global $user;
if (!empty($user->uid) && empty($user->data['overlay_message_dismissed']) && (!isset($user->data['overlay']) || $user->data['overlay']) && user_access('access overlay')) {
$build = array();
if (empty($user->uid) || !user_access('access overlay')) {
return $build;
}
$user_data = drupal_container()->get('user.data')->get('overlay', $user->uid);
if (empty($user_data['message_dismissed']) && (!isset($user_data['enabled']) || $user_data['enabled'])) {
$build = array(
'#theme' => 'overlay_disable_message',
'#weight' => -99,
......@@ -418,9 +424,6 @@ function overlay_disable_message() {
)
);
}
else {
$build = array();
}
return $build;
}
......
......@@ -819,13 +819,13 @@ protected function prepareEnvironment() {
$this->originalContainer = clone drupal_container();
$this->originalLanguage = $language_interface;
$this->originalConfigDirectories = $GLOBALS['config_directories'];
$this->originalThemeKey = $GLOBALS['theme_key'];
$this->originalTheme = $GLOBALS['theme'];
$this->originalThemeKey = isset($GLOBALS['theme_key']) ? $GLOBALS['theme_key'] : NULL;
$this->originalTheme = isset($GLOBALS['theme']) ? $GLOBALS['theme'] : NULL;
// Save further contextual information.
$this->originalFileDirectory = variable_get('file_public_path', conf_path() . '/files');
$this->originalProfile = drupal_get_profile();
$this->originalUser = clone $user;
$this->originalUser = isset($user) ? clone $user : NULL;
// Ensure that the current session is not changed by the new environment.
drupal_save_session(FALSE);
......
......@@ -29,6 +29,7 @@ public function setUp() {
// Path to the database dump files.
$this->databaseDumpFiles = array(
drupal_get_path('module', 'system') . '/tests/upgrade/drupal-7.filled.standard_all.database.php.gz',
drupal_get_path('module', 'system') . '/tests/upgrade/drupal-7.user_data.database.php',
);
parent::setUp();
}
......@@ -96,5 +97,28 @@ public function testFilledStandardUpgrade() {
$blog_type = node_type_load('blog');
$this->assertEqual($blog_type->module, 'node', "Content type 'blog' has been reassigned from the blog module to the node module.");
$this->assertEqual($blog_type->base, 'node_content', "The base string used to construct callbacks corresponding to content type 'Blog' has been reassigned to 'node_content'.");
// Check that user data has been migrated correctly.
$query = db_query('SELECT * FROM {users_data}');
$userdata = array();
$i = 0;
foreach ($query as $row) {
$i++;
$userdata[$row->uid][$row->module][$row->name] = $row;
}
// Check that the correct amount of rows exist.
$this->assertEqual($i, 5);
// Check that the data has been converted correctly.
$this->assertEqual(unserialize($userdata[1]['contact']['enabled']->value), 1);
$this->assertEqual($userdata[1]['contact']['enabled']->serialized, 1);
$this->assertEqual(unserialize($userdata[2]['contact']['enabled']->value), 0);
$this->assertEqual(unserialize($userdata[1]['overlay']['enabled']->value), 1);
$this->assertEqual(unserialize($userdata[2]['overlay']['enabled']->value), 1);
$this->assertEqual(unserialize($userdata[1]['overlay']['message_dismissed']->value), 1);
$this->assertFalse(isset($userdata[2]['overlay']['message_dismissed']));
// Make sure that only the garbage is remaining in the helper table.
$this->assertEqual(db_query('SELECT COUNT(*) FROM {_d7_users_data}')->fetchField(), 2);
}
}
<?php
/**
* @file
* Database additions for role tests. Used in
* \Drupal\system\Tests\Upgrade\FilledStandardUpgradePathTest.
*
* This dump only contains data and schema components relevant for user data
* upgrade tests. The drupal-7.filed.database.php.gz file is imported before
* this dump, so the two form the database structure expected in tests
* altogether.
*/
db_update('users')
->condition('uid', 1)
->fields(array(
'data' => serialize(array(
'contact' => 1,
'overlay_message_dismissed' => '1',
'overlay' => '1',
'garbage' => 'data',
)),
))
->execute();
db_update('users')
->condition('uid', 2)
->fields(array(
'data' => serialize(array(
'contact' => '0',
'overlay' => 1,
'more' => array('garbage', 'data'),
)),
))
->execute();
......@@ -2,12 +2,13 @@
/**
* @file
* Contains Drupal\system\UserBundle.
* Contains Drupal\user\UserBundle.
*/
namespace Drupal\user;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\HttpKernel\Bundle\Bundle;
/**
......@@ -16,10 +17,13 @@
class UserBundle extends Bundle {
/**
* Overrides Bundle::build().
* Overrides Symfony\Component\HttpKernel\Bundle\Bundle::build().
*/
public function build(ContainerBuilder $container) {
$container->register('access_check.user.register', 'Drupal\user\Access\RegisterAccessCheck')
->addTag('access_check');
$container
->register('user.data', 'Drupal\user\UserData')
->addArgument(new Reference('database'));
}
}
<?php
/**
* @file
* Contains Drupal\user\UserData.
*/
namespace Drupal\user;
use Drupal\Core\Database\Connection;
/**
* Defines the user data service.
*/
class UserData implements UserDataInterface {
/**
* The database connection to use.
*
* @var \Drupal\Core\Database\Connection
*/
protected $connection;
/**
* Constructs a new user data service.
*
* @param \Drupal\Core\Database\Connection $connection
* The database connection to use.
*/
public function __construct(Connection $connection) {
$this->connection = $connection;
}
/**
* Implements \Drupal\user\UserDataInterface::get().
*/
public function get($module, $uid = NULL, $name = NULL) {
$query = $this->connection->select('users_data', 'ud')
->fields('ud')
->condition('module', $module);
if (isset($uid)) {
$query->condition('uid', $uid);
}
if (isset($name)) {
$query->condition('name', $name);
}
$result = $query->execute();
// If $module, $uid, and $name was passed, return the value.
if (isset($name) && isset($uid)) {
$result = $result->fetchAllAssoc('uid');
if (isset($result[$uid])) {
return $result[$uid]->serialized ? unserialize($result[$uid]->value) : $result[$uid]->value;
}
return NULL;
}
// If $module and $uid was passed, return the name/value pairs.
elseif (isset($uid)) {
$return = array();
foreach ($result as $record) {
$return[$record->name] = ($record->serialized ? unserialize($record->value) : $record->value);
}
return $return;
}
// If $module and $name was passed, return the uid/value pairs.
elseif (isset($name)) {
$return = array();
foreach ($result as $record) {
$return[$record->uid] = ($record->serialized ? unserialize($record->value) : $record->value);
}
return $return;
}
// If only $module was passed, return data keyed by uid and name.
else {
$return = array();
foreach ($result as $record) {
$return[$record->uid][$record->name] = ($record->serialized ? unserialize($record->value) : $record->value);
}
return $return;
}
}
/**
* Implements \Drupal\user\UserDataInterface::set().
*/
public function set($module, $uid, $name, $value) {
$serialized = 0;
if (!is_scalar($value)) {
$value = serialize($value);
$serialized = 1;
}
$this->connection->merge('users_data')
->key(array(
'uid' => $uid,
'module' => $module,
'name' => $name,
))
->fields(array(
'value' => $value,
'serialized' => $serialized,
))
->execute();
}
/**
* Implements \Drupal\user\UserDataInterface::delete().
*/
public function delete($module = NULL, $uid = NULL, $name = NULL) {
$query = $this->connection->delete('users_data');
if (isset($module)) {
$query->condition('module', $module);
}
if (isset($uid)) {
$query->condition('uid', $uid);
}
if (isset($name)) {
$query->condition('name', $name);
}
$query->execute();
}
}
<?php
/**
* @file
* Contains Drupal\user\UserDataInterface.
*/
namespace Drupal\user;
use Drupal\Core\Database\Connection;
/**
* Defines the user data service interface.
*/
interface UserDataInterface {
/**
* Returns data stored for a user account.
*
* @param string $module
* The name of the module the data is associated with.
* @param integer $uid
* (optional) The user account ID the data is associated with.
* @param string $name
* (optional) The name of the data key.
*
* @return mixed|array
* The requested user account data, depending on the arguments passed:
* - For $module, $name, and $uid, the stored value is returned, or NULL if
* no value was found.
* - For $module and $uid, an associative array is returned that contains
* the stored data name/value pairs.
* - For $module and $name, an associative array is returned whose keys are
* user IDs and whose values contain the stored values.
* - For $module only, an associative array is returned that contains all
* existing data for $module in all user accounts, keyed first by user ID
* and $name second.
*/
public function get($module, $uid = NULL, $name = NULL);
/**
* Stores data for a user account.
*
* @param string $module
* The name of the module the data is associated with.
* @param integer $uid
* The user account ID the data is associated with.
* @param string $name
* The name of the data key.
* @param mixed $value
* The value to store. Non-scalar values are serialized automatically.
*
* @return void
*/
public function set($module, $uid, $name, $value);
/**
* Deletes data stored for a user account.
*
* @param string|array $module
* (optional) The name of the module the data is associated with. Can also
* be an array to delete the data of multiple modules.
* @param integer|array $uid
* (optional) The user account ID the data is associated with. If omitted,
* all data for $module is deleted. Can also be an array of IDs to delete
* the data of multiple user accounts.
* @param string $name
* (optional) The name of the data key. If omitted, all data associated with
* $module and $uid is deleted.
*
* @return void
*/
public function delete($module = NULL, $uid = NULL, $name = NULL);
}
......@@ -24,7 +24,6 @@ class UserStorageController extends DatabaseStorageController {
*/
function attachLoad(&$queried_users, $load_revision = FALSE) {
foreach ($queried_users as $key => $record) {
$queried_users[$key]->data = unserialize($record->data);
$queried_users[$key]->roles = array();
if ($record->uid) {
$queried_users[$record->uid]->roles[DRUPAL_AUTHENTICATED_RID] = DRUPAL_AUTHENTICATED_RID;
......@@ -96,10 +95,10 @@ protected function preSave(EntityInterface $entity) {
$entity->roles = array_filter($entity->roles);
}
// Move account cancellation information into $entity->data.
// Store account cancellation information.
foreach (array('user_cancel_method', 'user_cancel_notify') as $key) {
if (isset($entity->{$key})) {
$entity->data[$key] = $entity->{$key};
drupal_container()->get('user.data')->set('user', $entity->id(), substr($key, 5), $entity->{$key});
}
}
}
......@@ -179,5 +178,6 @@ protected function postDelete($entities) {
db_delete('authmap')
->condition('uid', array_keys($entities), 'IN')
->execute();