1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * Adhoc task handling migrating data to the new messaging table schema. 19 * 20 * @package core_message 21 * @copyright 2018 Mark Nelson <markn@moodle.com> 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25 namespace core_message\task; 26 27 defined('MOODLE_INTERNAL') || die(); 28 29 /** 30 * Class handling migrating data to the new messaging table schema. 31 * 32 * @package core_message 33 * @copyright 2018 Mark Nelson <markn@moodle.com> 34 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 35 */ 36 class migrate_message_data extends \core\task\adhoc_task { 37 38 /** 39 * Run the migration task. 40 */ 41 public function execute() { 42 global $DB; 43 44 $userid = $this->get_custom_data()->userid; 45 46 // Get the user's preference. 47 $hasbeenmigrated = get_user_preferences('core_message_migrate_data', false, $userid); 48 49 if (!$hasbeenmigrated) { 50 // To determine if we should update the preference. 51 $updatepreference = true; 52 53 // Get all the users the current user has received a message from. 54 $sql = "SELECT DISTINCT(useridfrom) 55 FROM {message} m 56 WHERE useridto = ? 57 UNION 58 SELECT DISTINCT(useridfrom) 59 FROM {message_read} m 60 WHERE useridto = ?"; 61 $users = $DB->get_records_sql($sql, [$userid, $userid]); 62 63 // Get all the users the current user has messaged. 64 $sql = "SELECT DISTINCT(useridto) 65 FROM {message} m 66 WHERE useridfrom = ? 67 UNION 68 SELECT DISTINCT(useridto) 69 FROM {message_read} m 70 WHERE useridfrom = ?"; 71 $users = $users + $DB->get_records_sql($sql, [$userid, $userid]); 72 if (!empty($users)) { 73 // Loop through each user and migrate the data. 74 foreach ($users as $otheruserid => $user) { 75 $ids = [$userid, $otheruserid]; 76 sort($ids); 77 $key = implode('_', $ids); 78 79 // Set the lock data. 80 $timeout = 5; // In seconds. 81 $locktype = 'core_message_migrate_data'; 82 83 // Get an instance of the currently configured lock factory. 84 $lockfactory = \core\lock\lock_config::get_lock_factory($locktype); 85 86 // See if we can grab this lock. 87 if ($lock = $lockfactory->get_lock($key, $timeout)) { 88 try { 89 $transaction = $DB->start_delegated_transaction(); 90 $this->migrate_data($userid, $otheruserid); 91 $transaction->allow_commit(); 92 } catch (\Throwable $e) { 93 throw $e; 94 } finally { 95 $lock->release(); 96 } 97 } else { 98 // Couldn't get a lock, move on to next user but make sure we don't update user preference so 99 // we still try again. 100 $updatepreference = false; 101 continue; 102 } 103 } 104 } 105 106 if ($updatepreference) { 107 set_user_preference('core_message_migrate_data', true, $userid); 108 } else { 109 // Throwing an exception in the task will mean that it isn't removed from the queue and is tried again. 110 throw new \moodle_exception('Task failed.'); 111 } 112 } 113 } 114 115 /** 116 * Helper function to deal with migrating the data. 117 * 118 * @param int $userid The current user id. 119 * @param int $otheruserid The user id of the other user in the conversation. 120 * @throws \dml_exception 121 */ 122 private function migrate_data($userid, $otheruserid) { 123 global $DB; 124 125 if ($userid == $otheruserid) { 126 // Since 3.7, pending self-conversations should be migrated during the upgrading process so shouldn't be any 127 // self-conversations on the legacy tables. However, this extra-check has been added just in case. 128 $conversation = \core_message\api::get_self_conversation($userid); 129 if (empty($conversation)) { 130 $conversation = \core_message\api::create_conversation( 131 \core_message\api::MESSAGE_CONVERSATION_TYPE_SELF, 132 [$userid] 133 ); 134 } 135 $conversationid = $conversation->id; 136 } else if (!$conversationid = \core_message\api::get_conversation_between_users([$userid, $otheruserid])) { 137 $conversation = \core_message\api::create_conversation( 138 \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, 139 [ 140 $userid, 141 $otheruserid 142 ] 143 ); 144 $conversationid = $conversation->id; 145 } 146 147 // First, get the rows from the 'message' table. 148 $select = "(useridfrom = ? AND useridto = ?) OR (useridfrom = ? AND useridto = ?)"; 149 $params = [$userid, $otheruserid, $otheruserid, $userid]; 150 $messages = $DB->get_recordset_select('message', $select, $params, 'id ASC'); 151 foreach ($messages as $message) { 152 if ($message->notification) { 153 $this->migrate_notification($message, false); 154 } else { 155 $this->migrate_message($conversationid, $message); 156 } 157 } 158 $messages->close(); 159 160 // Ok, all done, delete the records from the 'message' table. 161 $DB->delete_records_select('message', $select, $params); 162 163 // Now, get the rows from the 'message_read' table. 164 $messages = $DB->get_recordset_select('message_read', $select, $params, 'id ASC'); 165 foreach ($messages as $message) { 166 if ($message->notification) { 167 $this->migrate_notification($message, true); 168 } else { 169 $this->migrate_message($conversationid, $message); 170 } 171 } 172 $messages->close(); 173 174 // Ok, all done, delete the records from the 'message_read' table. 175 $DB->delete_records_select('message_read', $select, $params); 176 } 177 178 /** 179 * Helper function to deal with migrating an individual notification. 180 * 181 * @param \stdClass $notification 182 * @param bool $isread Was the notification read? 183 * @throws \dml_exception 184 */ 185 private function migrate_notification($notification, $isread) { 186 global $DB; 187 188 $tabledata = new \stdClass(); 189 $tabledata->useridfrom = $notification->useridfrom; 190 $tabledata->useridto = $notification->useridto; 191 $tabledata->subject = $notification->subject; 192 $tabledata->fullmessage = $notification->fullmessage; 193 $tabledata->fullmessageformat = $notification->fullmessageformat ?? FORMAT_MOODLE; 194 $tabledata->fullmessagehtml = $notification->fullmessagehtml; 195 $tabledata->smallmessage = $notification->smallmessage; 196 $tabledata->component = $notification->component; 197 $tabledata->eventtype = $notification->eventtype; 198 $tabledata->contexturl = $notification->contexturl; 199 $tabledata->contexturlname = $notification->contexturlname; 200 $tabledata->timeread = $notification->timeread ?? null; 201 $tabledata->timecreated = $notification->timecreated; 202 203 $newid = $DB->insert_record('notifications', $tabledata); 204 205 // Check if there is a record to move to the new 'message_popup_notifications' table. 206 if ($mp = $DB->get_record('message_popup', ['messageid' => $notification->id, 'isread' => (int) $isread])) { 207 $mpn = new \stdClass(); 208 $mpn->notificationid = $newid; 209 $DB->insert_record('message_popup_notifications', $mpn); 210 211 $DB->delete_records('message_popup', ['id' => $mp->id]); 212 } 213 } 214 215 /** 216 * Helper function to deal with migrating an individual message. 217 * 218 * @param int $conversationid The conversation between the two users. 219 * @param \stdClass $message The message from either the 'message' or 'message_read' table 220 * @throws \dml_exception 221 */ 222 private function migrate_message($conversationid, $message) { 223 global $DB; 224 225 // Create the object we will be inserting into the database. 226 $tabledata = new \stdClass(); 227 $tabledata->useridfrom = $message->useridfrom; 228 $tabledata->conversationid = $conversationid; 229 $tabledata->subject = $message->subject; 230 $tabledata->fullmessage = $message->fullmessage; 231 $tabledata->fullmessageformat = $message->fullmessageformat ?? FORMAT_MOODLE; 232 $tabledata->fullmessagehtml = $message->fullmessagehtml; 233 $tabledata->smallmessage = $message->smallmessage; 234 $tabledata->timecreated = $message->timecreated; 235 236 $messageid = $DB->insert_record('messages', $tabledata); 237 238 // Check if we need to mark this message as deleted for the user from. 239 if ($message->timeuserfromdeleted) { 240 $mua = new \stdClass(); 241 $mua->userid = $message->useridfrom; 242 $mua->messageid = $messageid; 243 $mua->action = \core_message\api::MESSAGE_ACTION_DELETED; 244 $mua->timecreated = $message->timeuserfromdeleted; 245 246 $DB->insert_record('message_user_actions', $mua); 247 } 248 249 // Check if we need to mark this message as deleted for the user to. 250 if ($message->timeusertodeleted and ($message->useridfrom != $message->useridto)) { 251 $mua = new \stdClass(); 252 $mua->userid = $message->useridto; 253 $mua->messageid = $messageid; 254 $mua->action = \core_message\api::MESSAGE_ACTION_DELETED; 255 $mua->timecreated = $message->timeusertodeleted; 256 257 $DB->insert_record('message_user_actions', $mua); 258 } 259 260 // Check if we need to mark this message as read for the user to (it is always read by the user from). 261 // Note - we do an isset() check here because this column only exists in the 'message_read' table. 262 if (isset($message->timeread)) { 263 $mua = new \stdClass(); 264 $mua->userid = $message->useridto; 265 $mua->messageid = $messageid; 266 $mua->action = \core_message\api::MESSAGE_ACTION_READ; 267 $mua->timecreated = $message->timeread; 268 269 $DB->insert_record('message_user_actions', $mua); 270 } 271 } 272 273 /** 274 * Queues the task. 275 * 276 * @param int $userid 277 */ 278 public static function queue_task($userid) { 279 // Let's set up the adhoc task. 280 $task = new \core_message\task\migrate_message_data(); 281 $task->set_custom_data( 282 [ 283 'userid' => $userid 284 ] 285 ); 286 287 // Queue it. 288 \core\task\manager::queue_adhoc_task($task, true); 289 } 290 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body