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.
   1  <?php
   2  
   3  // This file is part of Moodle - http://moodle.org/
   4  //
   5  // Moodle is free software: you can redistribute it and/or modify
   6  // it under the terms of the GNU General Public License as published by
   7  // the Free Software Foundation, either version 3 of the License, or
   8  // (at your option) any later version.
   9  //
  10  // Moodle is distributed in the hope that it will be useful,
  11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13  // GNU General Public License for more details.
  14  //
  15  // You should have received a copy of the GNU General Public License
  16  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  17  
  18  /**
  19   * @package mod_lesson
  20   * @subpackage backup-moodle2
  21   * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
  22   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  /**
  26   * Define all the restore steps that will be used by the restore_lesson_activity_task
  27   */
  28  
  29  /**
  30   * Structure step to restore one lesson activity
  31   */
  32  class restore_lesson_activity_structure_step extends restore_activity_structure_step {
  33      // Store the answers as they're received but only process them at the
  34      // end of the lesson
  35      protected $answers = array();
  36  
  37      protected function define_structure() {
  38  
  39          $paths = array();
  40          $userinfo = $this->get_setting_value('userinfo');
  41  
  42          $paths[] = new restore_path_element('lesson', '/activity/lesson');
  43          $paths[] = new restore_path_element('lesson_page', '/activity/lesson/pages/page');
  44          $paths[] = new restore_path_element('lesson_answer', '/activity/lesson/pages/page/answers/answer');
  45          $paths[] = new restore_path_element('lesson_override', '/activity/lesson/overrides/override');
  46          if ($userinfo) {
  47              $paths[] = new restore_path_element('lesson_attempt', '/activity/lesson/pages/page/answers/answer/attempts/attempt');
  48              $paths[] = new restore_path_element('lesson_grade', '/activity/lesson/grades/grade');
  49              $paths[] = new restore_path_element('lesson_branch', '/activity/lesson/pages/page/branches/branch');
  50              $paths[] = new restore_path_element('lesson_highscore', '/activity/lesson/highscores/highscore');
  51              $paths[] = new restore_path_element('lesson_timer', '/activity/lesson/timers/timer');
  52          }
  53  
  54          // Return the paths wrapped into standard activity structure
  55          return $this->prepare_activity_structure($paths);
  56      }
  57  
  58      protected function process_lesson($data) {
  59          global $DB;
  60  
  61          $data = (object)$data;
  62          $oldid = $data->id;
  63          $data->course = $this->get_courseid();
  64  
  65          // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset.
  66          // See MDL-9367.
  67          $data->available = $this->apply_date_offset($data->available);
  68          $data->deadline = $this->apply_date_offset($data->deadline);
  69  
  70          // The lesson->highscore code was removed in MDL-49581.
  71          // Remove it if found in the backup file.
  72          if (isset($data->showhighscores)) {
  73              unset($data->showhighscores);
  74          }
  75          if (isset($data->highscores)) {
  76              unset($data->highscores);
  77          }
  78  
  79          // Supply items that maybe missing from previous versions.
  80          if (!isset($data->completionendreached)) {
  81              $data->completionendreached = 0;
  82          }
  83          if (!isset($data->completiontimespent)) {
  84              $data->completiontimespent = 0;
  85          }
  86  
  87          if (!isset($data->intro)) {
  88              $data->intro = '';
  89              $data->introformat = FORMAT_HTML;
  90          }
  91  
  92          // Compatibility with old backups with maxtime and timed fields.
  93          if (!isset($data->timelimit)) {
  94              if (isset($data->timed) && isset($data->maxtime) && $data->timed) {
  95                  $data->timelimit = 60 * $data->maxtime;
  96              } else {
  97                  $data->timelimit = 0;
  98              }
  99          }
 100          // insert the lesson record
 101          $newitemid = $DB->insert_record('lesson', $data);
 102          // immediately after inserting "activity" record, call this
 103          $this->apply_activity_instance($newitemid);
 104      }
 105  
 106      protected function process_lesson_page($data) {
 107          global $DB;
 108  
 109          $data = (object)$data;
 110          $oldid = $data->id;
 111          $data->lessonid = $this->get_new_parentid('lesson');
 112  
 113          // We'll remap all the prevpageid and nextpageid at the end, once all pages have been created
 114  
 115          $newitemid = $DB->insert_record('lesson_pages', $data);
 116          $this->set_mapping('lesson_page', $oldid, $newitemid, true); // Has related fileareas
 117      }
 118  
 119      protected function process_lesson_answer($data) {
 120          global $DB;
 121  
 122          $data = (object)$data;
 123          $data->lessonid = $this->get_new_parentid('lesson');
 124          $data->pageid = $this->get_new_parentid('lesson_page');
 125          $data->answer = $data->answer_text;
 126  
 127          // Set a dummy mapping to get the old ID so that it can be used by get_old_parentid when
 128          // processing attempts. It will be corrected in after_execute
 129          $this->set_mapping('lesson_answer', $data->id, 0, true); // Has related fileareas.
 130  
 131          // Answers need to be processed in order, so we store them in an
 132          // instance variable and insert them in the after_execute stage
 133          $this->answers[$data->id] = $data;
 134      }
 135  
 136      protected function process_lesson_attempt($data) {
 137          global $DB;
 138  
 139          $data = (object)$data;
 140          $oldid = $data->id;
 141          $data->lessonid = $this->get_new_parentid('lesson');
 142          $data->pageid = $this->get_new_parentid('lesson_page');
 143  
 144          // We use the old answerid here as the answer isn't created until after_execute
 145          $data->answerid = $this->get_old_parentid('lesson_answer');
 146          $data->userid = $this->get_mappingid('user', $data->userid);
 147  
 148          $newitemid = $DB->insert_record('lesson_attempts', $data);
 149          $this->set_mapping('lesson_attempt', $oldid, $newitemid, true); // Has related fileareas.
 150      }
 151  
 152      protected function process_lesson_grade($data) {
 153          global $DB;
 154  
 155          $data = (object)$data;
 156          $oldid = $data->id;
 157          $data->lessonid = $this->get_new_parentid('lesson');
 158          $data->userid = $this->get_mappingid('user', $data->userid);
 159  
 160          $newitemid = $DB->insert_record('lesson_grades', $data);
 161          $this->set_mapping('lesson_grade', $oldid, $newitemid);
 162      }
 163  
 164      protected function process_lesson_branch($data) {
 165          global $DB;
 166  
 167          $data = (object)$data;
 168          $oldid = $data->id;
 169          $data->lessonid = $this->get_new_parentid('lesson');
 170          $data->pageid = $this->get_new_parentid('lesson_page');
 171          $data->userid = $this->get_mappingid('user', $data->userid);
 172  
 173          $newitemid = $DB->insert_record('lesson_branch', $data);
 174      }
 175  
 176      protected function process_lesson_highscore($data) {
 177          // Do not process any high score data.
 178          // high scores were removed in Moodle 3.0 See MDL-49581.
 179      }
 180  
 181      protected function process_lesson_timer($data) {
 182          global $DB;
 183  
 184          $data = (object)$data;
 185          $oldid = $data->id;
 186          $data->lessonid = $this->get_new_parentid('lesson');
 187          $data->userid = $this->get_mappingid('user', $data->userid);
 188          // Supply item that maybe missing from previous versions.
 189          if (!isset($data->completed)) {
 190              $data->completed = 0;
 191          }
 192          $newitemid = $DB->insert_record('lesson_timer', $data);
 193      }
 194  
 195      /**
 196       * Process a lesson override restore
 197       * @param object $data The data in object form
 198       * @return void
 199       */
 200      protected function process_lesson_override($data) {
 201          global $DB;
 202  
 203          $data = (object)$data;
 204          $oldid = $data->id;
 205  
 206          // Based on userinfo, we'll restore user overides or no.
 207          $userinfo = $this->get_setting_value('userinfo');
 208  
 209          // Skip user overrides if we are not restoring userinfo.
 210          if (!$userinfo && !is_null($data->userid)) {
 211              return;
 212          }
 213  
 214          $data->lessonid = $this->get_new_parentid('lesson');
 215  
 216          if (!is_null($data->userid)) {
 217              $data->userid = $this->get_mappingid('user', $data->userid);
 218          }
 219          if (!is_null($data->groupid)) {
 220              $data->groupid = $this->get_mappingid('group', $data->groupid);
 221          }
 222  
 223          // Skip if there is no user and no group data.
 224          if (empty($data->userid) && empty($data->groupid)) {
 225              return;
 226          }
 227  
 228          $data->available = $this->apply_date_offset($data->available);
 229          $data->deadline = $this->apply_date_offset($data->deadline);
 230  
 231          $newitemid = $DB->insert_record('lesson_overrides', $data);
 232  
 233          // Add mapping, restore of logs needs it.
 234          $this->set_mapping('lesson_override', $oldid, $newitemid);
 235      }
 236  
 237      protected function after_execute() {
 238          global $DB;
 239  
 240          // Answers must be sorted by id to ensure that they're shown correctly
 241          ksort($this->answers);
 242          foreach ($this->answers as $answer) {
 243              $newitemid = $DB->insert_record('lesson_answers', $answer);
 244              $this->set_mapping('lesson_answer', $answer->id, $newitemid, true);
 245  
 246              // Update the lesson attempts to use the newly created answerid
 247              $DB->set_field('lesson_attempts', 'answerid', $newitemid, array(
 248                      'lessonid' => $answer->lessonid,
 249                      'pageid' => $answer->pageid,
 250                      'answerid' => $answer->id));
 251          }
 252  
 253          // Add lesson files, no need to match by itemname (just internally handled context).
 254          $this->add_related_files('mod_lesson', 'intro', null);
 255          $this->add_related_files('mod_lesson', 'mediafile', null);
 256          // Add lesson page files, by lesson_page itemname
 257          $this->add_related_files('mod_lesson', 'page_contents', 'lesson_page');
 258          $this->add_related_files('mod_lesson', 'page_answers', 'lesson_answer');
 259          $this->add_related_files('mod_lesson', 'page_responses', 'lesson_answer');
 260          $this->add_related_files('mod_lesson', 'essay_responses', 'lesson_attempt');
 261          $this->add_related_files('mod_lesson', 'essay_answers', 'lesson_attempt');
 262  
 263          // Remap all the restored prevpageid and nextpageid now that we have all the pages and their mappings
 264          $rs = $DB->get_recordset('lesson_pages', array('lessonid' => $this->task->get_activityid()),
 265                                   '', 'id, prevpageid, nextpageid');
 266          foreach ($rs as $page) {
 267              $page->prevpageid = (empty($page->prevpageid)) ? 0 : $this->get_mappingid('lesson_page', $page->prevpageid);
 268              $page->nextpageid = (empty($page->nextpageid)) ? 0 : $this->get_mappingid('lesson_page', $page->nextpageid);
 269              $DB->update_record('lesson_pages', $page);
 270          }
 271          $rs->close();
 272  
 273          // Remap all the restored 'jumpto' fields now that we have all the pages and their mappings
 274          $rs = $DB->get_recordset('lesson_answers', array('lessonid' => $this->task->get_activityid()),
 275                                   '', 'id, jumpto');
 276          foreach ($rs as $answer) {
 277              if ($answer->jumpto > 0) {
 278                  $answer->jumpto = $this->get_mappingid('lesson_page', $answer->jumpto);
 279                  $DB->update_record('lesson_answers', $answer);
 280              }
 281          }
 282          $rs->close();
 283  
 284          // Remap all the restored 'nextpageid' fields now that we have all the pages and their mappings.
 285          $rs = $DB->get_recordset('lesson_branch', array('lessonid' => $this->task->get_activityid()),
 286                                   '', 'id, nextpageid');
 287          foreach ($rs as $answer) {
 288              if ($answer->nextpageid > 0) {
 289                  $answer->nextpageid = $this->get_mappingid('lesson_page', $answer->nextpageid);
 290                  $DB->update_record('lesson_branch', $answer);
 291              }
 292          }
 293          $rs->close();
 294  
 295          // Replay the upgrade step 2015030301
 296          // to clean lesson answers that should be plain text.
 297          // 1 = LESSON_PAGE_SHORTANSWER, 8 = LESSON_PAGE_NUMERICAL, 20 = LESSON_PAGE_BRANCHTABLE.
 298  
 299          $sql = 'SELECT a.*
 300                    FROM {lesson_answers} a
 301                    JOIN {lesson_pages} p ON p.id = a.pageid
 302                   WHERE a.answerformat <> :format
 303                     AND a.lessonid = :lessonid
 304                     AND p.qtype IN (1, 8, 20)';
 305          $badanswers = $DB->get_recordset_sql($sql, array('lessonid' => $this->task->get_activityid(), 'format' => FORMAT_MOODLE));
 306  
 307          foreach ($badanswers as $badanswer) {
 308              // Strip tags from answer text and convert back the format to FORMAT_MOODLE.
 309              $badanswer->answer = strip_tags($badanswer->answer);
 310              $badanswer->answerformat = FORMAT_MOODLE;
 311              $DB->update_record('lesson_answers', $badanswer);
 312          }
 313          $badanswers->close();
 314  
 315          // Replay the upgrade step 2015032700.
 316          // Delete any orphaned lesson_branch record.
 317          if ($DB->get_dbfamily() === 'mysql') {
 318              $sql = "DELETE {lesson_branch}
 319                        FROM {lesson_branch}
 320                   LEFT JOIN {lesson_pages}
 321                          ON {lesson_branch}.pageid = {lesson_pages}.id
 322                       WHERE {lesson_pages}.id IS NULL";
 323          } else {
 324              $sql = "DELETE FROM {lesson_branch}
 325                 WHERE NOT EXISTS (
 326                           SELECT 'x' FROM {lesson_pages}
 327                            WHERE {lesson_branch}.pageid = {lesson_pages}.id)";
 328          }
 329  
 330          $DB->execute($sql);
 331      }
 332  }