diff --git a/core/includes/session.inc b/core/includes/session.inc index b9b8fbc..9ffd3d1 100644 --- a/core/includes/session.inc +++ b/core/includes/session.inc @@ -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; diff --git a/core/modules/block/block.install b/core/modules/block/block.install index 56b91c3..a5bd3c4 100644 --- a/core/modules/block/block.install +++ b/core/modules/block/block.install @@ -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; } @@ -354,6 +358,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. */ diff --git a/core/modules/block/block.module b/core/modules/block/block.module index 7340c6b..bff7d54 100644 --- a/core/modules/block/block.module +++ b/core/modules/block/block.module @@ -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); diff --git a/core/modules/contact/contact.install b/core/modules/contact/contact.install index 9ea5451..8f97f0c 100644 --- a/core/modules/contact/contact.install +++ b/core/modules/contact/contact.install @@ -22,6 +22,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. * * @ingroup config_upgrade @@ -69,6 +80,26 @@ function contact_update_8002() { } /** + * 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. */ diff --git a/core/modules/contact/contact.module b/core/modules/contact/contact.module index 585f011..87f0e50 100644 --- a/core/modules/contact/contact.module +++ b/core/modules/contact/contact.module @@ -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); + } } /** diff --git a/core/modules/openid/lib/Drupal/openid/Tests/OpenIDRegistrationTest.php b/core/modules/openid/lib/Drupal/openid/Tests/OpenIDRegistrationTest.php index debb9cd..51c2160 100644 --- a/core/modules/openid/lib/Drupal/openid/Tests/OpenIDRegistrationTest.php +++ b/core/modules/openid/lib/Drupal/openid/Tests/OpenIDRegistrationTest.php @@ -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); diff --git a/core/modules/overlay/overlay.install b/core/modules/overlay/overlay.install index 2df860b..588f871 100644 --- a/core/modules/overlay/overlay.install +++ b/core/modules/overlay/overlay.install @@ -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(); +} diff --git a/core/modules/overlay/overlay.module b/core/modules/overlay/overlay.module index 8e6e65b..8c00f8b 100644 --- a/core/modules/overlay/overlay.module +++ b/core/modules/overlay/overlay.module @@ -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' => 'fieldset', '#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; } diff --git a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php index 60a9a77..72ec628 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php @@ -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); diff --git a/core/modules/system/lib/Drupal/system/Tests/Upgrade/FilledStandardUpgradePathTest.php b/core/modules/system/lib/Drupal/system/Tests/Upgrade/FilledStandardUpgradePathTest.php index e1df73e..41b3d84 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Upgrade/FilledStandardUpgradePathTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Upgrade/FilledStandardUpgradePathTest.php @@ -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); } } diff --git a/core/modules/system/tests/upgrade/drupal-7.user_data.database.php b/core/modules/system/tests/upgrade/drupal-7.user_data.database.php new file mode 100644 index 0000000..d55f4bf --- /dev/null +++ b/core/modules/system/tests/upgrade/drupal-7.user_data.database.php @@ -0,0 +1,35 @@ +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(); diff --git a/core/modules/user/lib/Drupal/user/UserBundle.php b/core/modules/user/lib/Drupal/user/UserBundle.php index 9ae8936..a4e7d8d 100644 --- a/core/modules/user/lib/Drupal/user/UserBundle.php +++ b/core/modules/user/lib/Drupal/user/UserBundle.php @@ -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')); } } diff --git a/core/modules/user/lib/Drupal/user/UserData.php b/core/modules/user/lib/Drupal/user/UserData.php new file mode 100644 index 0000000..e607d87 --- /dev/null +++ b/core/modules/user/lib/Drupal/user/UserData.php @@ -0,0 +1,121 @@ +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(); + } + +} diff --git a/core/modules/user/lib/Drupal/user/UserDataInterface.php b/core/modules/user/lib/Drupal/user/UserDataInterface.php new file mode 100644 index 0000000..b56baf4 --- /dev/null +++ b/core/modules/user/lib/Drupal/user/UserDataInterface.php @@ -0,0 +1,75 @@ + $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(); + drupal_container()->get('user.data')->delete(NULL, array_keys($entities)); } } diff --git a/core/modules/user/user.api.php b/core/modules/user/user.api.php index cb7b4e5..3b5f4ca 100644 --- a/core/modules/user/user.api.php +++ b/core/modules/user/user.api.php @@ -229,11 +229,6 @@ function hook_user_operations() { * * This hook is invoked before the user account is saved to the database. * - * Modules that want to store properties in the serialized {users}.data column, - * which is automatically loaded whenever a user account object is loaded, may - * add their properties to $account->data in order to have their data serialized - * on save. - * * @param $account * The user account object. * @@ -241,10 +236,9 @@ function hook_user_operations() { * @see hook_user_update() */ function hook_user_presave($account) { - // Make sure that our form value 'mymodule_foo' is stored as - // 'mymodule_bar' in the 'data' (serialized) column. + // Ensure that our value is an array. if (isset($account->mymodule_foo)) { - $account->data['mymodule_bar'] = $account->mymodule_foo; + $account->mymodule_foo = (array) $account->mymodule_foo; } } diff --git a/core/modules/user/user.install b/core/modules/user/user.install index 28511d3..058ebbd 100644 --- a/core/modules/user/user.install +++ b/core/modules/user/user.install @@ -129,13 +129,6 @@ function user_schema() { 'default' => '', 'description' => 'E-mail address used for initial account creation.', ), - 'data' => array( - 'type' => 'blob', - 'not null' => FALSE, - 'size' => 'big', - 'serialize' => TRUE, - 'description' => 'A serialized array of name value pairs that are related to the user. Any form values posted during user edit are stored and are loaded into the $user object during user_load(). Use of this field is discouraged and it will likely disappear in a future version of Drupal.', - ), ), 'indexes' => array( 'access' => array('access'), @@ -267,6 +260,54 @@ function user_schema() { ), ); + $schema['users_data'] = array( + 'description' => 'Stores module data as key/value pairs per user.', + 'fields' => array( + 'uid' => array( + 'description' => 'Primary key: {users}.uid for user.', + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'module' => array( + 'description' => 'The name of the module declaring the variable.', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'name' => array( + 'description' => 'The identifier of the data.', + 'type' => 'varchar', + 'length' => 128, + 'not null' => TRUE, + 'default' => '', + ), + 'value' => array( + 'description' => 'The value.', + 'type' => 'blob', + 'not null' => FALSE, + 'size' => 'big', + ), + 'serialized' => array( + 'description' => 'Whether value is serialized.', + 'type' => 'int', + 'size' => 'tiny', + 'unsigned' => TRUE, + 'default' => 0, + ), + ), + 'primary key' => array('uid', 'module', 'name'), + 'indexes' => array( + 'module' => array('module'), + 'name' => array('name'), + ), + 'foreign keys' => array( + 'uid' => array('users' => 'uid'), + ), + ); + $schema['users_roles'] = array( 'description' => 'Maps users to roles.', 'fields' => array( @@ -326,7 +367,6 @@ function user_install() { 'mail' => 'placeholder-for-uid-1', 'created' => REQUEST_TIME, 'status' => 1, - 'data' => NULL, )) ->execute(); @@ -788,6 +828,142 @@ function user_update_8013() { } /** + * Create new {users_data} table. + */ +function user_update_8014() { + // Create the {users_data} table. + db_create_table('users_data', array( + 'description' => 'Stores module data as key/value pairs per user.', + 'fields' => array( + 'uid' => array( + 'description' => 'Primary key: {users}.uid for user.', + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'module' => array( + 'description' => 'The name of the module declaring the variable.', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'name' => array( + 'description' => 'The identifier of the data.', + 'type' => 'varchar', + 'length' => 128, + 'not null' => TRUE, + 'default' => '', + ), + 'value' => array( + 'description' => 'The value.', + 'type' => 'blob', + 'not null' => FALSE, + 'size' => 'big', + ), + 'serialized' => array( + 'description' => 'Whether value is serialized.', + 'type' => 'int', + 'size' => 'tiny', + 'unsigned' => TRUE, + 'default' => 0, + ), + ), + 'primary key' => array('uid', 'module', 'name'), + 'indexes' => array( + 'module' => array('module'), + 'name' => array('name'), + ), + 'foreign keys' => array( + 'uid' => array('users' => 'uid'), + ), + )); + + // Create backup table for data migration. + // Since the origin/owner of individual values in {users}.data is unknown, + // other modules need to migrate their existing values from {_d7_users_data}. + db_create_table('_d7_users_data', array( + 'description' => 'Backup of {users}.data for migration.', + 'fields' => array( + 'uid' => array( + 'description' => 'Primary Key: {users}.uid for user.', + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'name' => array( + 'description' => 'The name of the variable.', + 'type' => 'varchar', + 'length' => 128, + 'not null' => TRUE, + 'default' => '', + ), + 'value' => array( + 'description' => 'The serialized value of the variable.', + 'type' => 'blob', + 'not null' => FALSE, + 'size' => 'big', + 'serialize' => TRUE, + ), + ), + 'primary key' => array('uid', 'name'), + 'foreign keys' => array( + 'uid' => array('users' => 'uid'), + ), + )); +} + +/** + * Move existing {users}.data into {_d7_users_data} migration table. + */ +function user_update_8015(&$sandbox) { + if (!isset($sandbox['progress'])) { + $sandbox['progress'] = 0; + // The anonymous user cannot have data, so start with uid 1. + $sandbox['last'] = 0; + $sandbox['max'] = db_query('SELECT COUNT(uid) FROM {users} WHERE uid > 0')->fetchField(); + } + + // Process 20 user records at a time. E.g., if there are 10 data keys per user + // record, that leads to an insert query with 200 values. + $result = db_query_range('SELECT uid, data FROM {users} WHERE uid > :uid ORDER BY uid ASC', 0, 20, array(':uid' => $sandbox['last']))->fetchAllKeyed(); + $query = db_insert('_d7_users_data')->fields(array('uid', 'name', 'value')); + $has_values = FALSE; + foreach ($result as $uid => $data) { + $sandbox['progress']++; + $sandbox['last'] = $uid; + if (empty($data)) { + continue; + } + $data = unserialize($data); + if (!empty($data) && is_array($data)) { + $has_values = TRUE; + foreach ($data as $name => $value) { + $query->values(array( + 'uid' => $uid, + 'name' => $name, + 'value' => serialize($value), + )); + } + } + } + if ($has_values) { + $query->execute(); + } + + $sandbox['#finished'] = empty($sandbox['max']) ? 1 : ($sandbox['progress'] / $sandbox['max']); +} + +/** + * Drop {users}.data column. + */ +function user_update_8016() { + db_drop_field('users', 'data'); +} + +/** * @} End of "addtogroup updates-7.x-to-8.x". */ diff --git a/core/modules/user/user.module b/core/modules/user/user.module index 93c48e1..8d73b82 100644 --- a/core/modules/user/user.module +++ b/core/modules/user/user.module @@ -2805,12 +2805,11 @@ function user_node_load($nodes, $types) { } // Fetch name and data for these users. - $user_fields = db_query("SELECT uid, name, data FROM {users} WHERE uid IN (:uids)", array(':uids' => $uids))->fetchAllAssoc('uid'); + $user_names = db_query("SELECT uid, name FROM {users} WHERE uid IN (:uids)", array(':uids' => $uids))->fetchAllKeyed(); // Add these values back into the node objects. foreach ($uids as $nid => $uid) { - $nodes[$nid]->name = $user_fields[$uid]->name; - $nodes[$nid]->data = $user_fields[$uid]->data; + $nodes[$nid]->name = $user_names[$uid]; } } @@ -2927,6 +2926,8 @@ function user_modules_uninstalled($modules) { db_delete('role_permission') ->condition('module', $modules, 'IN') ->execute(); + // Remove any potentially orphan module data stored for users. + drupal_container()->get('user.data')->delete($modules); } /** diff --git a/core/modules/user/user.pages.inc b/core/modules/user/user.pages.inc index 20af863..d45112d 100644 --- a/core/modules/user/user.pages.inc +++ b/core/modules/user/user.pages.inc @@ -388,13 +388,14 @@ function user_cancel_confirm($account, $timestamp = 0, $hashed_pass = '') { $current = REQUEST_TIME; // Basic validation of arguments. - if (isset($account->data['user_cancel_method']) && !empty($timestamp) && !empty($hashed_pass)) { + $account_data = drupal_container()->get('user.data')->get('user', $account->id()); + if (isset($account_data['cancel_method']) && !empty($timestamp) && !empty($hashed_pass)) { // Validate expiration and hashed password/login. if ($timestamp <= $current && $current - $timestamp < $timeout && $account->uid && $timestamp >= $account->login && $hashed_pass == user_pass_rehash($account->pass, $timestamp, $account->login)) { $edit = array( - 'user_cancel_notify' => isset($account->data['user_cancel_notify']) ? $account->data['user_cancel_notify'] : config('user.settings')->get('notify.status_canceled'), + 'user_cancel_notify' => isset($account_data['cancel_notify']) ? $account_data['cancel_notify'] : config('user.settings')->get('notify.status_canceled'), ); - user_cancel($edit, $account->uid, $account->data['user_cancel_method']); + user_cancel($edit, $account->id(), $account_data['cancel_method']); // Since user_cancel() is not invoked via Form API, batch processing needs // to be invoked manually and should redirect to the front page after // completion. diff --git a/core/modules/user/user.views.inc b/core/modules/user/user.views.inc index 170759a..52e3402 100644 --- a/core/modules/user/user.views.inc +++ b/core/modules/user/user.views.inc @@ -316,14 +316,6 @@ function user_views_data() { ), ); - $data['users']['data'] = array( - 'title' => t('Data'), - 'help' => t('Provide serialized data of the user'), - 'field' => array( - 'id' => 'serialized', - ), - ); - // users_roles table $data['users_roles']['table']['group'] = t('User'); diff --git a/core/scripts/run-tests.sh b/core/scripts/run-tests.sh index 5029d13..e728932 100755 --- a/core/scripts/run-tests.sh +++ b/core/scripts/run-tests.sh @@ -369,7 +369,7 @@ function simpletest_script_run_one_test($test_id, $test_class) { try { // Bootstrap Drupal. - drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL); + drupal_bootstrap(DRUPAL_BOOTSTRAP_CODE); simpletest_classloader_register();