From 41bc5aa587a4bb10bd4e9c5d0a2581bc385cd5ab Mon Sep 17 00:00:00 2001 From: Mark Nelson Date: Thu, 15 Feb 2018 19:47:08 +0800 Subject: [PATCH] MDL-61255 core_message: created task to handle migration of data --- lib/moodlelib.php | 24 +- message/classes/task/migrate_message_data.php | 262 ++++++++++++++++++ version.php | 2 +- 3 files changed, 286 insertions(+), 2 deletions(-) create mode 100644 message/classes/task/migrate_message_data.php diff --git a/lib/moodlelib.php b/lib/moodlelib.php index d6a5dab7fa9..e932026d880 100644 --- a/lib/moodlelib.php +++ b/lib/moodlelib.php @@ -4405,7 +4405,7 @@ function authenticate_user_login($username, $password, $ignorelockout=false, &$f * @return stdClass A {@link $USER} object - BC only, do not use */ function complete_user_login($user) { - global $CFG, $USER, $SESSION; + global $CFG, $DB, $USER, $SESSION; \core\session\manager::login_user($user); @@ -4429,6 +4429,28 @@ function complete_user_login($user) { ); $event->trigger(); + // Queue migrating the messaging data, if we need to. + if (!get_user_preferences('core_message_migrate_data', false, $USER->id)) { + // Check if there are any legacy messages to migrate. + $sql = "SELECT id + FROM {message} m + WHERE useridfrom = ? + OR useridto = ?"; + $messageexists = $DB->record_exists_sql($sql, [$USER->id, $USER->id]); + + $sql = "SELECT id + FROM {message_read} m + WHERE useridfrom = ? + OR useridto = ?"; + $messagereadexists = $DB->record_exists_sql($sql, [$USER->id, $USER->id]); + + if ($messageexists || $messagereadexists) { + \core_message\task\migrate_message_data::queue_task($USER->id); + } else { + set_user_preference('core_message_migrate_data', true, $USER->id); + } + } + if (isguestuser()) { // No need to continue when user is THE guest. return $USER; diff --git a/message/classes/task/migrate_message_data.php b/message/classes/task/migrate_message_data.php new file mode 100644 index 00000000000..7b7235858fd --- /dev/null +++ b/message/classes/task/migrate_message_data.php @@ -0,0 +1,262 @@ +. + +/** + * Adhoc task handling migrating data to the new messaging table schema. + * + * @package core_message + * @copyright 2018 Mark Nelson + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace core_message\task; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Class handling migrating data to the new messaging table schema. + * + * @package core_message + * @copyright 2018 Mark Nelson + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class migrate_message_data extends \core\task\adhoc_task { + + /** + * Run the migration task. + */ + public function execute() { + global $DB; + + $userid = $this->get_custom_data()->userid; + + // Get the user's preference. + $hasbeenmigrated = get_user_preferences('core_message_migrate_data', false, $userid); + + if (!$hasbeenmigrated) { + // To determine if we should update the preference. + $updatepreference = true; + + // Get all the users the current user has received a message from. + $sql = "SELECT DISTINCT(useridfrom) + FROM {message} m + WHERE useridto = ? + UNION + SELECT DISTINCT(useridfrom) + FROM {message_read} m + WHERE useridto = ?"; + $users = $DB->get_records_sql($sql, [$userid, $userid]); + + // Get all the users the current user has messaged. + $sql = "SELECT DISTINCT(useridto) + FROM {message} m + WHERE useridfrom = ? + UNION + SELECT DISTINCT(useridto) + FROM {message_read} m + WHERE useridfrom = ?"; + $users = $users + $DB->get_records_sql($sql, [$userid, $userid]); + if (!empty($users)) { + // Loop through each user and migrate the data. + foreach ($users as $otheruserid => $user) { + $ids = [$userid, $otheruserid]; + sort($ids); + $key = implode('_', $ids); + + // Set the lock data. + $timeout = 5; // In seconds. + $locktype = 'core_message_migrate_data'; + + // Get an instance of the currently configured lock factory. + $lockfactory = \core\lock\lock_config::get_lock_factory($locktype); + + // See if we can grab this lock. + if ($lock = $lockfactory->get_lock($key, $timeout)) { + try { + $transaction = $DB->start_delegated_transaction(); + $this->migrate_data($userid, $otheruserid); + $transaction->allow_commit(); + } catch (\Throwable $e) { + $updatepreference = false; + } + + $lock->release(); + } else { + // Couldn't get a lock, move on to next user but make sure we don't update user preference so + // we still try again. + $updatepreference = false; + continue; + } + } + } + + if ($updatepreference) { + set_user_preference('core_message_migrate_data', true, $userid); + } else { + // Throwing an exception in the task will mean that it isn't removed from the queue and is tried again. + throw new \moodle_exception('Task failed.'); + } + } + } + + /** + * Helper function to deal with migrating the data. + * + * @param int $userid The current user id. + * @param int $otheruserid The user id of the other user in the conversation. + * @throws \dml_exception + */ + private function migrate_data($userid, $otheruserid) { + global $DB; + + if (!$conversationid = \core_message\api::get_conversation_between_users([$userid, $otheruserid])) { + $conversationid = \core_message\api::create_conversation_between_users([$userid, $otheruserid]); + } + + // First, get the rows from the 'message' table. + $select = "(useridfrom = ? AND useridto = ?) OR (useridfrom = ? AND useridto = ?)"; + $params = [$userid, $otheruserid, $otheruserid, $userid]; + $messages = $DB->get_recordset_select('message', $select, $params, 'id ASC'); + foreach ($messages as $message) { + if ($message->notification) { + $this->migrate_notification($message); + } else { + $this->migrate_message($conversationid, $message); + } + } + $messages->close(); + + // Ok, all done, delete the records from the 'message' table. + $DB->delete_records_select('message', $select, $params); + + // Now, get the rows from the 'message_read' table. + $messages = $DB->get_recordset_select('message_read', $select, $params, 'id ASC'); + foreach ($messages as $message) { + if ($message->notification) { + $this->migrate_notification($message); + } else { + $this->migrate_message($conversationid, $message); + } + } + $messages->close(); + + // Ok, all done, delete the records from the 'message_read' table. + $DB->delete_records_select('message_read', $select, $params); + } + + /** + * Helper function to deal with migrating an individual notification. + * + * @param \stdClass $notification + * @throws \dml_exception + */ + private function migrate_notification($notification) { + global $DB; + + $tabledata = new \stdClass(); + $tabledata->useridfrom = $notification->useridfrom; + $tabledata->useridto = $notification->useridto; + $tabledata->subject = $notification->subject; + $tabledata->fullmessage = $notification->fullmessage; + $tabledata->fullmessageformat = $notification->fullmessageformat; + $tabledata->fullmessagehtml = $notification->fullmessagehtml; + $tabledata->smallmessage = $notification->smallmessage; + $tabledata->component = $notification->component; + $tabledata->eventtype = $notification->eventtype; + $tabledata->contexturl = $notification->contexturl; + $tabledata->contexturlname = $notification->contexturlname; + $tabledata->timeread = $notification->timeread ?? null; + $tabledata->timecreated = $notification->timecreated; + + $DB->insert_record('notifications', $tabledata); + } + + /** + * Helper function to deal with migrating an individual message. + * + * @param int $conversationid The conversation between the two users. + * @param \stdClass $message The message from either the 'message' or 'message_read' table + * @throws \dml_exception + */ + private function migrate_message($conversationid, $message) { + global $DB; + + // Create the object we will be inserting into the database. + $tabledata = new \stdClass(); + $tabledata->useridfrom = $message->useridfrom; + $tabledata->conversationid = $conversationid; + $tabledata->subject = $message->subject; + $tabledata->fullmessage = $message->fullmessage; + $tabledata->fullmessageformat = $message->fullmessageformat; + $tabledata->fullmessagehtml = $message->fullmessagehtml; + $tabledata->smallmessage = $message->smallmessage; + $tabledata->timecreated = $message->timecreated; + + $messageid = $DB->insert_record('messages', $tabledata); + + // Check if we need to mark this message as deleted for the user from. + if ($message->timeuserfromdeleted) { + $mua = new \stdClass(); + $mua->userid = $message->useridfrom; + $mua->messageid = $messageid; + $mua->action = \core_message\api::MESSAGE_ACTION_DELETED; + $mua->timecreated = $message->timeuserfromdeleted; + + $DB->insert_record('message_user_actions', $mua); + } + + // Check if we need to mark this message as deleted for the user to. + if ($message->timeusertodeleted) { + $mua = new \stdClass(); + $mua->userid = $message->useridto; + $mua->messageid = $messageid; + $mua->action = \core_message\api::MESSAGE_ACTION_DELETED; + $mua->timecreated = $message->timeusertodeleted; + + $DB->insert_record('message_user_actions', $mua); + } + + // Check if we need to mark this message as read for the user to (it is always read by the user from). + // Note - we do an isset() check here because this column only exists in the 'message_read' table. + if (isset($message->timeread)) { + $mua = new \stdClass(); + $mua->userid = $message->useridto; + $mua->messageid = $messageid; + $mua->action = \core_message\api::MESSAGE_ACTION_READ; + $mua->timecreated = $message->timeread; + + $DB->insert_record('message_user_actions', $mua); + } + } + + /** + * Queues the task. + * + * @param int $userid + */ + public static function queue_task($userid) { + // Let's set up the adhoc task. + $task = new \core_message\task\migrate_message_data(); + $task->set_custom_data( + [ + 'userid' => $userid + ] + ); + + // Queue it. + \core\task\manager::queue_adhoc_task($task, true); + } +} diff --git a/version.php b/version.php index f6a151f0d84..748d11af984 100644 --- a/version.php +++ b/version.php @@ -29,7 +29,7 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2018040500.00; // YYYYMMDD = weekly release date of this DEV branch. +$version = 2018040900.00; // YYYYMMDD = weekly release date of this DEV branch. // RR = release increments - 00 in DEV branches. // .XX = incremental changes.