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 400] [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   * The main interface for recycle bin methods.
  19   *
  20   * @package    tool_recyclebin
  21   * @copyright  2015 University of Kent
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  namespace tool_recyclebin;
  26  
  27  defined('MOODLE_INTERNAL') || die();
  28  
  29  define('TOOL_RECYCLEBIN_COURSE_BIN_FILEAREA', 'recyclebin_course');
  30  
  31  /**
  32   * Represents a course's recyclebin.
  33   *
  34   * @package    tool_recyclebin
  35   * @copyright  2015 University of Kent
  36   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  37   */
  38  class course_bin extends base_bin {
  39  
  40      /**
  41       * @var int The course id.
  42       */
  43      protected $_courseid;
  44  
  45      /**
  46       * Constructor.
  47       *
  48       * @param int $courseid Course ID.
  49       */
  50      public function __construct($courseid) {
  51          $this->_courseid = $courseid;
  52      }
  53  
  54      /**
  55       * Is this recyclebin enabled?
  56       *
  57       * @return bool true if enabled, false if not.
  58       */
  59      public static function is_enabled() {
  60          return get_config('tool_recyclebin', 'coursebinenable');
  61      }
  62  
  63      /**
  64       * Returns an item from the recycle bin.
  65       *
  66       * @param int $itemid Item ID to retrieve.
  67       * @return \stdClass the item.
  68       */
  69      public function get_item($itemid) {
  70          global $DB;
  71  
  72          return $DB->get_record('tool_recyclebin_course', array(
  73              'id' => $itemid
  74          ), '*', MUST_EXIST);
  75      }
  76  
  77      /**
  78       * Returns a list of items in the recycle bin for this course.
  79       *
  80       * @return array the list of items.
  81       */
  82      public function get_items() {
  83          global $DB;
  84  
  85          return $DB->get_records('tool_recyclebin_course', array(
  86              'courseid' => $this->_courseid
  87          ));
  88      }
  89  
  90      /**
  91       * Store a course module in the recycle bin.
  92       *
  93       * @param \stdClass $cm Course module
  94       * @throws \moodle_exception
  95       */
  96      public function store_item($cm) {
  97          global $CFG, $DB;
  98  
  99          require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
 100  
 101          // Get more information.
 102          $modinfo = get_fast_modinfo($cm->course);
 103  
 104          if (!isset($modinfo->cms[$cm->id])) {
 105              return; // Can't continue without the module information.
 106          }
 107  
 108          $cminfo = $modinfo->cms[$cm->id];
 109  
 110          // Check backup/restore support.
 111          if (!plugin_supports('mod', $cminfo->modname , FEATURE_BACKUP_MOODLE2)) {
 112              return;
 113          }
 114  
 115          // As far as recycle bin is using MODE_AUTOMATED, it observes the backup_auto_storage
 116          // settings (storing backups @ real location and potentially not including files).
 117          // For recycle bin we want to ensure that backup files are always stored in Moodle file
 118          // area and always contain the users' files. In order to achieve that, we hack the
 119          // setting here via $CFG->forced_plugin_settings, so it won't interfere other operations.
 120          // See MDL-65218 and MDL-35773 for more information.
 121          // This hack will be removed once recycle bin switches to use its own backup mode, with
 122          // own preferences and 100% separate from MOODLE_AUTOMATED.
 123          // TODO: Remove this as part of MDL-65228.
 124          $CFG->forced_plugin_settings['backup'] = ['backup_auto_storage' => 0, 'backup_auto_files' => 1];
 125  
 126          // Backup the activity.
 127          $user = get_admin();
 128          $controller = new \backup_controller(
 129              \backup::TYPE_1ACTIVITY,
 130              $cm->id,
 131              \backup::FORMAT_MOODLE,
 132              \backup::INTERACTIVE_NO,
 133              \backup::MODE_AUTOMATED,
 134              $user->id
 135          );
 136          $controller->execute_plan();
 137  
 138          // We don't need the forced setting anymore, hence unsetting it.
 139          // TODO: Remove this as part of MDL-65228.
 140          unset($CFG->forced_plugin_settings['backup']);
 141  
 142          // Grab the result.
 143          $result = $controller->get_results();
 144          if (!isset($result['backup_destination'])) {
 145              throw new \moodle_exception('Failed to backup activity prior to deletion.');
 146          }
 147  
 148          // Have finished with the controller, let's destroy it, freeing mem and resources.
 149          $controller->destroy();
 150  
 151          // Grab the filename.
 152          $file = $result['backup_destination'];
 153          if (!$file->get_contenthash()) {
 154              throw new \moodle_exception('Failed to backup activity prior to deletion (invalid file).');
 155          }
 156  
 157          // Record the activity, get an ID.
 158          $activity = new \stdClass();
 159          $activity->courseid = $cm->course;
 160          $activity->section = $cm->section;
 161          $activity->module = $cm->module;
 162          $activity->name = $cminfo->name;
 163          $activity->timecreated = time();
 164          $binid = $DB->insert_record('tool_recyclebin_course', $activity);
 165  
 166          // Create the location we want to copy this file to.
 167          $filerecord = array(
 168              'contextid' => \context_course::instance($this->_courseid)->id,
 169              'component' => 'tool_recyclebin',
 170              'filearea' => TOOL_RECYCLEBIN_COURSE_BIN_FILEAREA,
 171              'itemid' => $binid,
 172              'timemodified' => time()
 173          );
 174  
 175          // Move the file to our own special little place.
 176          $fs = get_file_storage();
 177          if (!$fs->create_file_from_storedfile($filerecord, $file)) {
 178              // Failed, cleanup first.
 179              $DB->delete_records('tool_recyclebin_course', array(
 180                  'id' => $binid
 181              ));
 182  
 183              throw new \moodle_exception("Failed to copy backup file to recyclebin.");
 184          }
 185  
 186          // Delete the old file.
 187          $file->delete();
 188  
 189          // Fire event.
 190          $event = \tool_recyclebin\event\course_bin_item_created::create(array(
 191              'objectid' => $binid,
 192              'context' => \context_course::instance($cm->course)
 193          ));
 194          $event->trigger();
 195      }
 196  
 197      /**
 198       * Restore an item from the recycle bin.
 199       *
 200       * @param \stdClass $item The item database record
 201       * @throws \moodle_exception
 202       */
 203      public function restore_item($item) {
 204          global $CFG, $OUTPUT, $PAGE;
 205  
 206          require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
 207  
 208          $user = get_admin();
 209  
 210          // Grab the course context.
 211          $context = \context_course::instance($this->_courseid);
 212  
 213          // Get the files..
 214          $fs = get_file_storage();
 215          $files = $fs->get_area_files($context->id, 'tool_recyclebin', TOOL_RECYCLEBIN_COURSE_BIN_FILEAREA, $item->id,
 216              'itemid, filepath, filename', false);
 217  
 218          if (empty($files)) {
 219              throw new \moodle_exception('Invalid recycle bin item!');
 220          }
 221  
 222          if (count($files) > 1) {
 223              throw new \moodle_exception('Too many files found!');
 224          }
 225  
 226          // Get the backup file.
 227          $file = reset($files);
 228  
 229          // Get a backup temp directory name and create it.
 230          $tempdir = \restore_controller::get_tempdir_name($context->id, $user->id);
 231          $fulltempdir = make_backup_temp_directory($tempdir);
 232  
 233          // Extract the backup to tempdir.
 234          $fb = get_file_packer('application/vnd.moodle.backup');
 235          $fb->extract_to_pathname($file, $fulltempdir);
 236  
 237          // Define the import.
 238          $controller = new \restore_controller(
 239              $tempdir,
 240              $this->_courseid,
 241              \backup::INTERACTIVE_NO,
 242              \backup::MODE_AUTOMATED,
 243              $user->id,
 244              \backup::TARGET_EXISTING_ADDING
 245          );
 246  
 247          // Prechecks.
 248          if (!$controller->execute_precheck()) {
 249              $results = $controller->get_precheck_results();
 250  
 251              // If errors are found then delete the file we created.
 252              if (!empty($results['errors'])) {
 253                  fulldelete($fulltempdir);
 254  
 255                  echo $OUTPUT->header();
 256                  $backuprenderer = $PAGE->get_renderer('core', 'backup');
 257                  echo $backuprenderer->precheck_notices($results);
 258                  echo $OUTPUT->continue_button(new \moodle_url('/course/view.php', array('id' => $this->_courseid)));
 259                  echo $OUTPUT->footer();
 260                  exit();
 261              }
 262          }
 263  
 264          // Run the import.
 265          $controller->execute_plan();
 266  
 267          // Have finished with the controller, let's destroy it, freeing mem and resources.
 268          $controller->destroy();
 269  
 270          // Fire event.
 271          $event = \tool_recyclebin\event\course_bin_item_restored::create(array(
 272              'objectid' => $item->id,
 273              'context' => $context
 274          ));
 275          $event->add_record_snapshot('tool_recyclebin_course', $item);
 276          $event->trigger();
 277  
 278          // Cleanup.
 279          fulldelete($fulltempdir);
 280          $this->delete_item($item);
 281      }
 282  
 283      /**
 284       * Delete an item from the recycle bin.
 285       *
 286       * @param \stdClass $item The item database record
 287       */
 288      public function delete_item($item) {
 289          global $DB;
 290  
 291          // Grab the course context.
 292          $context = \context_course::instance($this->_courseid, IGNORE_MISSING);
 293  
 294          if (!empty($context)) {
 295              // Delete the files.
 296              $fs = get_file_storage();
 297              $fs->delete_area_files($context->id, 'tool_recyclebin', TOOL_RECYCLEBIN_COURSE_BIN_FILEAREA, $item->id);
 298          } else {
 299              // Course context has been deleted. Find records using $item->id as this is unique for course bin recyclebin.
 300              $files = $DB->get_recordset('files', [
 301                  'component' => 'tool_recyclebin',
 302                  'filearea' => TOOL_RECYCLEBIN_COURSE_BIN_FILEAREA,
 303                  'itemid' => $item->id,
 304              ]);
 305              $fs = get_file_storage();
 306              foreach ($files as $filer) {
 307                  $file = $fs->get_file_instance($filer);
 308                  $file->delete();
 309              }
 310              $files->close();
 311          }
 312  
 313          // Delete the record.
 314          $DB->delete_records('tool_recyclebin_course', array(
 315              'id' => $item->id
 316          ));
 317  
 318          // The course might have been deleted, check we have a context.
 319          $context = \context_course::instance($item->courseid, \IGNORE_MISSING);
 320          if (!$context) {
 321              return;
 322          }
 323  
 324          // Fire event.
 325          $event = \tool_recyclebin\event\course_bin_item_deleted::create(array(
 326              'objectid' => $item->id,
 327              'context' => $context
 328          ));
 329          $event->add_record_snapshot('tool_recyclebin_course', $item);
 330          $event->trigger();
 331      }
 332  
 333      /**
 334       * Can we view items in this recycle bin?
 335       *
 336       * @return bool returns true if they can view, false if not
 337       */
 338      public function can_view() {
 339          $context = \context_course::instance($this->_courseid);
 340          return has_capability('tool/recyclebin:viewitems', $context);
 341      }
 342  
 343      /**
 344       * Can we restore items in this recycle bin?
 345       *
 346       * @return bool returns true if they can restore, false if not
 347       */
 348      public function can_restore() {
 349          $context = \context_course::instance($this->_courseid);
 350          return has_capability('tool/recyclebin:restoreitems', $context);
 351      }
 352  
 353      /**
 354       * Can we delete this?
 355       *
 356       * @return bool returns true if they can delete, false if not
 357       */
 358      public function can_delete() {
 359          $context = \context_course::instance($this->_courseid);
 360          return has_capability('tool/recyclebin:deleteitems', $context);
 361      }
 362  }