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 namespace tiny_autosave; 18 19 use stdClass; 20 21 /** 22 * Autosave Manager. 23 * 24 * @package tiny_autosave 25 * @copyright 2022 Andrew Lyons <andrew@nicols.co.uk> 26 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 27 */ 28 class autosave_manager { 29 30 /** @var int The contextid */ 31 protected $contextid; 32 33 /** @var string The page hash reference */ 34 protected $pagehash; 35 36 /** @var string The page instance reference */ 37 protected $pageinstance; 38 39 /** @var string The elementid for this editor */ 40 protected $elementid; 41 42 /** @var stdClass The user record */ 43 protected $user; 44 45 /** 46 * Constructor for the autosave manager. 47 * 48 * @param int $contextid The contextid of the session 49 * @param string $pagehash The page hash 50 * @param string $pageinstance The page instance 51 * @param string $elementid The element id 52 * @param null|stdClass $user The user object for the owner of the autosave 53 */ 54 public function __construct( 55 int $contextid, 56 string $pagehash, 57 string $pageinstance, 58 string $elementid, 59 ?stdClass $user = null 60 ) { 61 global $USER; 62 63 $this->contextid = $contextid; 64 $this->pagehash = $pagehash; 65 $this->pageinstance = $pageinstance; 66 $this->elementid = $elementid; 67 $this->user = $user ?? $USER; 68 } 69 70 /** 71 * Get the autosave record for this session. 72 * 73 * @return stdClass|null 74 */ 75 public function get_autosave_record(): ?stdClass { 76 global $DB; 77 78 $record = $DB->get_record('tiny_autosave', [ 79 'contextid' => $this->contextid, 80 'userid' => $this->user->id, 81 'pagehash' => $this->pagehash, 82 'elementid' => $this->elementid, 83 ]); 84 85 if (empty($record)) { 86 return null; 87 } 88 89 return $record; 90 } 91 92 /** 93 * Create an autosave record for the session. 94 * 95 * @param string $drafttext The draft text to save 96 * @param null|int $draftid The draft file area if one is used 97 * @return stdClass The autosave record 98 */ 99 public function create_autosave_record(string $drafttext, ?int $draftid = null): stdClass { 100 global $DB; 101 $record = (object) [ 102 'userid' => $this->user->id, 103 'contextid' => $this->contextid, 104 'pagehash' => $this->pagehash, 105 'pageinstance' => $this->pageinstance, 106 'elementid' => $this->elementid, 107 'drafttext' => $drafttext, 108 'timemodified' => time(), 109 ]; 110 111 if ($draftid) { 112 $record->draftid = $draftid; 113 } 114 115 $record->id = $DB->insert_record('tiny_autosave', $record); 116 117 return $record; 118 } 119 120 /** 121 * Update the text of the autosave session. 122 * 123 * @param string $drafttext The text to save 124 * @return stdClass The updated record 125 */ 126 public function update_autosave_record(string $drafttext): stdClass { 127 global $DB; 128 129 $record = $this->get_autosave_record(); 130 if ($record) { 131 $record->drafttext = $drafttext; 132 $record->timemodified = time(); 133 $DB->update_record('tiny_autosave', $record); 134 135 return $record; 136 } else { 137 return $this->create_autosave_record($drafttext); 138 } 139 } 140 141 /** 142 * Resume an autosave session, updating the draft file area if relevant. 143 * 144 * @param null|int $draftid The draft file area to update 145 * @return stdClass The updated autosave record 146 */ 147 public function resume_autosave_session(?int $draftid = null): stdClass { 148 $record = $this->get_autosave_record(); 149 if (!$record) { 150 return $this->create_autosave_record('', $draftid); 151 } 152 153 if ($this->is_autosave_stale($record)) { 154 // If the autosave record it stale, remove it and create a new, blank, record. 155 $this->remove_autosave_record(); 156 157 return $this->create_autosave_record('', $draftid); 158 } 159 160 if (empty($draftid)) { 161 // There is no file area to handle, so just return the record without any further changes. 162 return $record; 163 } 164 165 // This autosave is not stale, so update the draftid and move any files over to the new draft file area. 166 return $this->update_draftid_for_record($record, $draftid); 167 } 168 169 /** 170 * Check whether the autosave data is stale. 171 * 172 * Records are considered stale if either of the following conditions are true: 173 * - The record is older than the stale period 174 * - Any of the files in the draft area are newer than the autosave data itself 175 * 176 * @param stdClass $record The autosave record 177 * @return bool Whether the record is stale 178 */ 179 protected function is_autosave_stale(stdClass $record): bool { 180 $timemodified = $record->timemodified; 181 // TODO Create the UI for the stale period. 182 $staleperiod = get_config('tiny_autosave', 'staleperiod'); 183 if (empty($staleperiod)) { 184 $staleperiod = (4 * DAYSECS); 185 } 186 187 $stale = $timemodified < (time() - $staleperiod); 188 189 if (empty($record->draftid)) { 190 return $stale; 191 } 192 193 $fs = get_file_storage(); 194 $files = $fs->get_directory_files($record->contextid, 'user', 'draft', $record->draftid, '/', true, true); 195 196 $lastfilemodified = 0; 197 foreach ($files as $file) { 198 if ($record->timemodified < $file->get_timemodified()) { 199 $stale = true; 200 break; 201 } 202 } 203 204 return $stale; 205 } 206 207 /** 208 * Move the files relating to the autosave session to a new draft file area. 209 * 210 * @param stdClass $record The autosave record 211 * @param int $newdraftid The new draftid to move files to 212 * @return stdClass The updated autosave record 213 */ 214 protected function update_draftid_for_record(stdClass $record, int $newdraftid): stdClass { 215 global $CFG, $DB; 216 217 require_once("{$CFG->libdir}/filelib.php"); 218 219 // Copy all draft files from the old draft area. 220 $usercontext = \context_user::instance($this->user->id); 221 222 // This function copies all the files in one draft area, to another area (in this case it's 223 // another draft area). It also rewrites the text to @@PLUGINFILE@@ links. 224 $record->drafttext = file_save_draft_area_files( 225 $record->draftid, 226 $usercontext->id, 227 'user', 228 'draft', 229 $newdraftid, 230 [], 231 $record->drafttext 232 ); 233 234 // Final rewrite to the new draft area (convert the @@PLUGINFILES@@ again). 235 $record->drafttext = file_rewrite_pluginfile_urls( 236 $record->drafttext, 237 'draftfile.php', 238 $usercontext->id, 239 'user', 240 'draft', 241 $newdraftid 242 ); 243 244 $record->draftid = $newdraftid; 245 $record->pageinstance = $this->pageinstance; 246 $record->timemodified = time(); 247 248 $DB->update_record('tiny_autosave', $record); 249 250 return $record; 251 } 252 253 /** 254 * Remove the autosave record. 255 */ 256 public function remove_autosave_record(): void { 257 global $DB; 258 259 $DB->delete_records('tiny_autosave', [ 260 'contextid' => $this->contextid, 261 'userid' => $this->user->id, 262 'pagehash' => $this->pagehash, 263 'elementid' => $this->elementid, 264 ]); 265 } 266 267 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body