a2a76d1fe91431d7c415ac5c2d38ee996885c80f
[moodle.git] / message / classes / task / migrate_message_data.php
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/>.
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  */
25 namespace core_message\task;
27 defined('MOODLE_INTERNAL') || die();
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 {
38     /**
39      * Run the migration task.
40      */
41     public function execute() {
42         global $DB;
44         $userid = $this->get_custom_data()->userid;
46         // Get the user's preference.
47         $hasbeenmigrated = get_user_preferences('core_message_migrate_data', false, $userid);
49         if (!$hasbeenmigrated) {
50             // To determine if we should update the preference.
51             $updatepreference = true;
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]);
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);
79                     // Set the lock data.
80                     $timeout = 5; // In seconds.
81                     $locktype = 'core_message_migrate_data';
83                     // Get an instance of the currently configured lock factory.
84                     $lockfactory = \core\lock\lock_config::get_lock_factory($locktype);
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             }
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     }
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;
125         if (!$conversationid = \core_message\api::get_conversation_between_users([$userid, $otheruserid])) {
126             $conversationid = \core_message\api::create_conversation_between_users([$userid, $otheruserid]);
127         }
129         // First, get the rows from the 'message' table.
130         $select = "(useridfrom = ? AND useridto = ?) OR (useridfrom = ? AND useridto = ?)";
131         $params = [$userid, $otheruserid, $otheruserid, $userid];
132         $messages = $DB->get_recordset_select('message', $select, $params, 'id ASC');
133         foreach ($messages as $message) {
134             if ($message->notification) {
135                 $this->migrate_notification($message, false);
136             } else {
137                 $this->migrate_message($conversationid, $message);
138             }
139         }
140         $messages->close();
142         // Ok, all done, delete the records from the 'message' table.
143         $DB->delete_records_select('message', $select, $params);
145         // Now, get the rows from the 'message_read' table.
146         $messages = $DB->get_recordset_select('message_read', $select, $params, 'id ASC');
147         foreach ($messages as $message) {
148             if ($message->notification) {
149                 $this->migrate_notification($message, true);
150             } else {
151                 $this->migrate_message($conversationid, $message);
152             }
153         }
154         $messages->close();
156         // Ok, all done, delete the records from the 'message_read' table.
157         $DB->delete_records_select('message_read', $select, $params);
158     }
160     /**
161      * Helper function to deal with migrating an individual notification.
162      *
163      * @param \stdClass $notification
164      * @param bool $isread Was the notification read?
165      * @throws \dml_exception
166      */
167     private function migrate_notification($notification, $isread) {
168         global $DB;
170         $tabledata = new \stdClass();
171         $tabledata->useridfrom = $notification->useridfrom;
172         $tabledata->useridto = $notification->useridto;
173         $tabledata->subject = $notification->subject;
174         $tabledata->fullmessage = $notification->fullmessage;
175         $tabledata->fullmessageformat = $notification->fullmessageformat;
176         $tabledata->fullmessagehtml = $notification->fullmessagehtml;
177         $tabledata->smallmessage = $notification->smallmessage;
178         $tabledata->component = $notification->component;
179         $tabledata->eventtype = $notification->eventtype;
180         $tabledata->contexturl = $notification->contexturl;
181         $tabledata->contexturlname = $notification->contexturlname;
182         $tabledata->timeread = $notification->timeread ?? null;
183         $tabledata->timecreated = $notification->timecreated;
185         $newid = $DB->insert_record('notifications', $tabledata);
187         // Check if there is a record to move to the new 'message_popup_notifications' table.
188         if ($mp = $DB->get_record('message_popup', ['messageid' => $notification->id, 'isread' => (int) $isread])) {
189             $mpn = new \stdClass();
190             $mpn->notificationid = $newid;
191             $DB->insert_record('message_popup_notifications', $mpn);
193             $DB->delete_records('message_popup', ['id' => $mp->id]);
194         }
195     }
197     /**
198      * Helper function to deal with migrating an individual message.
199      *
200      * @param int $conversationid The conversation between the two users.
201      * @param \stdClass $message The message from either the 'message' or 'message_read' table
202      * @throws \dml_exception
203      */
204     private function migrate_message($conversationid, $message) {
205         global $DB;
207         // Create the object we will be inserting into the database.
208         $tabledata = new \stdClass();
209         $tabledata->useridfrom = $message->useridfrom;
210         $tabledata->conversationid = $conversationid;
211         $tabledata->subject = $message->subject;
212         $tabledata->fullmessage = $message->fullmessage;
213         $tabledata->fullmessageformat = $message->fullmessageformat;
214         $tabledata->fullmessagehtml = $message->fullmessagehtml;
215         $tabledata->smallmessage = $message->smallmessage;
216         $tabledata->timecreated = $message->timecreated;
218         $messageid = $DB->insert_record('messages', $tabledata);
220         // Check if we need to mark this message as deleted for the user from.
221         if ($message->timeuserfromdeleted) {
222             $mua = new \stdClass();
223             $mua->userid = $message->useridfrom;
224             $mua->messageid = $messageid;
225             $mua->action = \core_message\api::MESSAGE_ACTION_DELETED;
226             $mua->timecreated = $message->timeuserfromdeleted;
228             $DB->insert_record('message_user_actions', $mua);
229         }
231         // Check if we need to mark this message as deleted for the user to.
232         if ($message->timeusertodeleted) {
233             $mua = new \stdClass();
234             $mua->userid = $message->useridto;
235             $mua->messageid = $messageid;
236             $mua->action = \core_message\api::MESSAGE_ACTION_DELETED;
237             $mua->timecreated = $message->timeusertodeleted;
239             $DB->insert_record('message_user_actions', $mua);
240         }
242         // Check if we need to mark this message as read for the user to (it is always read by the user from).
243         // Note - we do an isset() check here because this column only exists in the 'message_read' table.
244         if (isset($message->timeread)) {
245             $mua = new \stdClass();
246             $mua->userid = $message->useridto;
247             $mua->messageid = $messageid;
248             $mua->action = \core_message\api::MESSAGE_ACTION_READ;
249             $mua->timecreated = $message->timeread;
251             $DB->insert_record('message_user_actions', $mua);
252         }
253     }
255     /**
256      * Queues the task.
257      *
258      * @param int $userid
259      */
260     public static function queue_task($userid) {
261         // Let's set up the adhoc task.
262         $task = new \core_message\task\migrate_message_data();
263         $task->set_custom_data(
264             [
265                 'userid' => $userid
266             ]
267         );
269         // Queue it.
270         \core\task\manager::queue_adhoc_task($task, true);
271     }