Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 39 and 401] [Versions 39 and 402] [Versions 39 and 403]

   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   * Save and load draft text while a user is still editing a form.
  19   *
  20   * @package    editor_atto
  21   * @copyright  2014 Damyon Wiese
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  define('AJAX_SCRIPT', true);
  26  
  27  require_once(__DIR__ . '/../../../config.php');
  28  require_once($CFG->libdir . '/filestorage/file_storage.php');
  29  
  30  // Clean up actions.
  31  $actions = array_map(function($actionparams) {
  32      $action = isset($actionparams['action']) ? $actionparams['action'] : null;
  33      $params = [];
  34      $keys = [
  35          'action' => PARAM_ALPHA,
  36          'contextid' => PARAM_INT,
  37          'elementid' => PARAM_ALPHANUMEXT,
  38          'pagehash' => PARAM_ALPHANUMEXT,
  39          'pageinstance' => PARAM_ALPHANUMEXT
  40      ];
  41  
  42      if ($action == 'save') {
  43          $keys['drafttext'] = PARAM_RAW;
  44      } else if ($action == 'resume') {
  45          $keys['draftid'] = PARAM_INT;
  46      }
  47  
  48      foreach ($keys as $key => $type) {
  49          // Replicate required_param().
  50          if (!isset($actionparams[$key])) {
  51              print_error('missingparam', '', '', $key);
  52          }
  53          $params[$key] = clean_param($actionparams[$key], $type);
  54      }
  55  
  56      return $params;
  57  }, isset($_REQUEST['actions']) ? $_REQUEST['actions'] : []);
  58  
  59  $now = time();
  60  // This is the oldest time any autosave text will be recovered from.
  61  // This is so that there is a good chance the draft files will still exist (there are many variables so
  62  // this is impossible to guarantee).
  63  $before = $now - 60*60*24*4;
  64  
  65  $context = context_system::instance();
  66  $PAGE->set_url('/lib/editor/atto/autosave-ajax.php');
  67  $PAGE->set_context($context);
  68  
  69  require_login();
  70  if (isguestuser()) {
  71      print_error('accessdenied', 'admin');
  72  }
  73  require_sesskey();
  74  
  75  if (!in_array('atto', explode(',', get_config('core', 'texteditors')))) {
  76      print_error('accessdenied', 'admin');
  77  }
  78  
  79  $responses = array();
  80  foreach ($actions as $actionparams) {
  81  
  82      $action = $actionparams['action'];
  83      $contextid = $actionparams['contextid'];
  84      $elementid = $actionparams['elementid'];
  85      $pagehash = $actionparams['pagehash'];
  86      $pageinstance = $actionparams['pageinstance'];
  87  
  88      if ($action === 'save') {
  89          $drafttext = $actionparams['drafttext'];
  90          $params = array('elementid' => $elementid,
  91                          'userid' => $USER->id,
  92                          'pagehash' => $pagehash,
  93                          'contextid' => $contextid);
  94  
  95          $record = $DB->get_record('editor_atto_autosave', $params);
  96          if ($record && $record->pageinstance != $pageinstance) {
  97              print_error('concurrent access from the same user is not supported');
  98              die();
  99          }
 100  
 101          if (!$record) {
 102              $record = new stdClass();
 103              $record->elementid = $elementid;
 104              $record->userid = $USER->id;
 105              $record->pagehash = $pagehash;
 106              $record->contextid = $contextid;
 107              $record->drafttext = $drafttext;
 108              $record->pageinstance = $pageinstance;
 109              $record->timemodified = $now;
 110  
 111              $DB->insert_record('editor_atto_autosave', $record);
 112  
 113              // No response means no error.
 114              $responses[] = null;
 115              continue;
 116          } else {
 117              $record->drafttext = $drafttext;
 118              $record->timemodified = time();
 119              $DB->update_record('editor_atto_autosave', $record);
 120  
 121              // No response means no error.
 122              $responses[] = null;
 123              continue;
 124          }
 125  
 126      } else if ($action == 'resume') {
 127          $params = array('elementid' => $elementid,
 128                          'userid' => $USER->id,
 129                          'pagehash' => $pagehash,
 130                          'contextid' => $contextid);
 131  
 132          $newdraftid = $actionparams['draftid'];
 133  
 134          $record = $DB->get_record('editor_atto_autosave', $params);
 135  
 136          if (!$record) {
 137              $record = new stdClass();
 138              $record->elementid = $elementid;
 139              $record->userid = $USER->id;
 140              $record->pagehash = $pagehash;
 141              $record->contextid = $contextid;
 142              $record->pageinstance = $pageinstance;
 143              $record->pagehash = $pagehash;
 144              $record->draftid = $newdraftid;
 145              $record->timemodified = time();
 146              $record->drafttext = '';
 147  
 148              $DB->insert_record('editor_atto_autosave', $record);
 149  
 150              // No response means no error.
 151              $responses[] = null;
 152              continue;
 153  
 154          } else {
 155              // Copy all draft files from the old draft area.
 156              $usercontext = context_user::instance($USER->id);
 157              $stale = $record->timemodified < $before;
 158              require_once($CFG->libdir . '/filelib.php');
 159  
 160              $fs = get_file_storage();
 161              $files = $fs->get_directory_files($usercontext->id, 'user', 'draft', $newdraftid, '/', true, true);
 162  
 163              $lastfilemodified = 0;
 164              foreach ($files as $file) {
 165                  $lastfilemodified = max($lastfilemodified, $file->get_timemodified());
 166              }
 167              if ($record->timemodified < $lastfilemodified) {
 168                  $stale = true;
 169              }
 170  
 171              if (!$stale) {
 172                  // This function copies all the files in one draft area, to another area (in this case it's
 173                  // another draft area). It also rewrites the text to @@PLUGINFILE@@ links.
 174                  $newdrafttext = file_save_draft_area_files($record->draftid,
 175                                                             $usercontext->id,
 176                                                             'user',
 177                                                             'draft',
 178                                                             $newdraftid,
 179                                                             array(),
 180                                                             $record->drafttext);
 181  
 182                  // Final rewrite to the new draft area (convert the @@PLUGINFILES@@ again).
 183                  $newdrafttext = file_rewrite_pluginfile_urls($newdrafttext,
 184                                                               'draftfile.php',
 185                                                               $usercontext->id,
 186                                                               'user',
 187                                                               'draft',
 188                                                               $newdraftid);
 189                  $record->drafttext = $newdrafttext;
 190  
 191                  $record->pageinstance = $pageinstance;
 192                  $record->draftid = $newdraftid;
 193                  $record->timemodified = time();
 194                  $DB->update_record('editor_atto_autosave', $record);
 195  
 196                  // A response means the draft has been restored and here is the auto-saved text.
 197                  $response = ['result' => $record->drafttext];
 198                  $responses[] = $response;
 199  
 200              } else {
 201                  $DB->delete_records('editor_atto_autosave', array('id' => $record->id));
 202  
 203                  // No response means no error.
 204                  $responses[] = null;
 205              }
 206              continue;
 207          }
 208  
 209      } else if ($action == 'reset') {
 210          $params = array('elementid' => $elementid,
 211                          'userid' => $USER->id,
 212                          'pagehash' => $pagehash,
 213                          'contextid' => $contextid);
 214  
 215          $DB->delete_records('editor_atto_autosave', $params);
 216          $responses[] = null;
 217          continue;
 218      }
 219  }
 220  
 221  echo json_encode($responses);