Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401]

   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  
 137          // When "backup_auto_activities" setting is disabled, activities can't be restored from recycle bin.
 138          $plan = $controller->get_plan();
 139          $activitiessettings = $plan->get_setting('activities');
 140          $settingsvalue = $activitiessettings->get_value();
 141          if (empty($settingsvalue)) {
 142              $controller->destroy();
 143              return;
 144          }
 145  
 146          $controller->execute_plan();
 147  
 148          // We don't need the forced setting anymore, hence unsetting it.
 149          // TODO: Remove this as part of MDL-65228.
 150          unset($CFG->forced_plugin_settings['backup']);
 151  
 152          // Grab the result.
 153          $result = $controller->get_results();
 154          if (!isset($result['backup_destination'])) {
 155              throw new \moodle_exception('Failed to backup activity prior to deletion.');
 156          }
 157  
 158          // Have finished with the controller, let's destroy it, freeing mem and resources.
 159          $controller->destroy();
 160  
 161          // Grab the filename.
 162          $file = $result['backup_destination'];
 163          if (!$file->get_contenthash()) {
 164              throw new \moodle_exception('Failed to backup activity prior to deletion (invalid file).');
 165          }
 166  
 167          // Record the activity, get an ID.
 168          $activity = new \stdClass();
 169          $activity->courseid = $cm->course;
 170          $activity->section = $cm->section;
 171          $activity->module = $cm->module;
 172          $activity->name = $cminfo->name;
 173          $activity->timecreated = time();
 174          $binid = $DB->insert_record('tool_recyclebin_course', $activity);
 175  
 176          // Create the location we want to copy this file to.
 177          $filerecord = array(
 178              'contextid' => \context_course::instance($this->_courseid)->id,
 179              'component' => 'tool_recyclebin',
 180              'filearea' => TOOL_RECYCLEBIN_COURSE_BIN_FILEAREA,
 181              'itemid' => $binid,
 182              'timemodified' => time()
 183          );
 184  
 185          // Move the file to our own special little place.
 186          $fs = get_file_storage();
 187          if (!$fs->create_file_from_storedfile($filerecord, $file)) {
 188              // Failed, cleanup first.
 189              $DB->delete_records('tool_recyclebin_course', array(
 190                  'id' => $binid
 191              ));
 192  
 193              throw new \moodle_exception("Failed to copy backup file to recyclebin.");
 194          }
 195  
 196          // Delete the old file.
 197          $file->delete();
 198  
 199          // Fire event.
 200          $event = \tool_recyclebin\event\course_bin_item_created::create(array(
 201              'objectid' => $binid,
 202              'context' => \context_course::instance($cm->course)
 203          ));
 204          $event->trigger();
 205      }
 206  
 207      /**
 208       * Restore an item from the recycle bin.
 209       *
 210       * @param \stdClass $item The item database record
 211       * @throws \moodle_exception
 212       */
 213      public function restore_item($item) {
 214          global $CFG, $OUTPUT, $PAGE;
 215  
 216          require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
 217  
 218          $user = get_admin();
 219  
 220          // Grab the course context.
 221          $context = \context_course::instance($this->_courseid);
 222  
 223          // Get the files..
 224          $fs = get_file_storage();
 225          $files = $fs->get_area_files($context->id, 'tool_recyclebin', TOOL_RECYCLEBIN_COURSE_BIN_FILEAREA, $item->id,
 226              'itemid, filepath, filename', false);
 227  
 228          if (empty($files)) {
 229              throw new \moodle_exception('Invalid recycle bin item!');
 230          }
 231  
 232          if (count($files) > 1) {
 233              throw new \moodle_exception('Too many files found!');
 234          }
 235  
 236          // Get the backup file.
 237          $file = reset($files);
 238  
 239          // Get a backup temp directory name and create it.
 240          $tempdir = \restore_controller::get_tempdir_name($context->id, $user->id);
 241          $fulltempdir = make_backup_temp_directory($tempdir);
 242  
 243          // Extract the backup to tempdir.
 244          $fb = get_file_packer('application/vnd.moodle.backup');
 245          $fb->extract_to_pathname($file, $fulltempdir);
 246  
 247          // As far as recycle bin is using MODE_AUTOMATED, it observes the General restore settings.
 248          // For recycle bin we want to ensure that backup files are always restore the users and groups information.
 249          // In order to achieve that, we hack the setting here via $CFG->forced_plugin_settings,
 250          // so it won't interfere other operations.
 251          // See MDL-65218 and MDL-35773 for more information.
 252          // This hack will be removed once recycle bin switches to use its own backup mode, with
 253          // own preferences and 100% separate from MOODLE_AUTOMATED.
 254          // TODO: Remove this as part of MDL-65228.
 255          $CFG->forced_plugin_settings['restore'] = ['restore_general_users' => 1, 'restore_general_groups' => 1];
 256  
 257          // Define the import.
 258          $controller = new \restore_controller(
 259              $tempdir,
 260              $this->_courseid,
 261              \backup::INTERACTIVE_NO,
 262              \backup::MODE_AUTOMATED,
 263              $user->id,
 264              \backup::TARGET_EXISTING_ADDING
 265          );
 266  
 267          // Prechecks.
 268          if (!$controller->execute_precheck()) {
 269              $results = $controller->get_precheck_results();
 270  
 271              // If errors are found then delete the file we created.
 272              if (!empty($results['errors'])) {
 273                  fulldelete($fulltempdir);
 274  
 275                  echo $OUTPUT->header();
 276                  $backuprenderer = $PAGE->get_renderer('core', 'backup');
 277                  echo $backuprenderer->precheck_notices($results);
 278                  echo $OUTPUT->continue_button(new \moodle_url('/course/view.php', array('id' => $this->_courseid)));
 279                  echo $OUTPUT->footer();
 280                  exit();
 281              }
 282          }
 283  
 284          // Run the import.
 285          $controller->execute_plan();
 286  
 287          // We don't need the forced setting anymore, hence unsetting it.
 288          // TODO: Remove this as part of MDL-65228.
 289          unset($CFG->forced_plugin_settings['restore']);
 290  
 291          // Have finished with the controller, let's destroy it, freeing mem and resources.
 292          $controller->destroy();
 293  
 294          // Fire event.
 295          $event = \tool_recyclebin\event\course_bin_item_restored::create(array(
 296              'objectid' => $item->id,
 297              'context' => $context
 298          ));
 299          $event->add_record_snapshot('tool_recyclebin_course', $item);
 300          $event->trigger();
 301  
 302          // Cleanup.
 303          fulldelete($fulltempdir);
 304          $this->delete_item($item);
 305      }
 306  
 307      /**
 308       * Delete an item from the recycle bin.
 309       *
 310       * @param \stdClass $item The item database record
 311       */
 312      public function delete_item($item) {
 313          global $DB;
 314  
 315          // Grab the course context.
 316          $context = \context_course::instance($this->_courseid, IGNORE_MISSING);
 317  
 318          if (!empty($context)) {
 319              // Delete the files.
 320              $fs = get_file_storage();
 321              $fs->delete_area_files($context->id, 'tool_recyclebin', TOOL_RECYCLEBIN_COURSE_BIN_FILEAREA, $item->id);
 322          } else {
 323              // Course context has been deleted. Find records using $item->id as this is unique for course bin recyclebin.
 324              $files = $DB->get_recordset('files', [
 325                  'component' => 'tool_recyclebin',
 326                  'filearea' => TOOL_RECYCLEBIN_COURSE_BIN_FILEAREA,
 327                  'itemid' => $item->id,
 328              ]);
 329              $fs = get_file_storage();
 330              foreach ($files as $filer) {
 331                  $file = $fs->get_file_instance($filer);
 332                  $file->delete();
 333              }
 334              $files->close();
 335          }
 336  
 337          // Delete the record.
 338          $DB->delete_records('tool_recyclebin_course', array(
 339              'id' => $item->id
 340          ));
 341  
 342          // The course might have been deleted, check we have a context.
 343          $context = \context_course::instance($item->courseid, \IGNORE_MISSING);
 344          if (!$context) {
 345              return;
 346          }
 347  
 348          // Fire event.
 349          $event = \tool_recyclebin\event\course_bin_item_deleted::create(array(
 350              'objectid' => $item->id,
 351              'context' => $context
 352          ));
 353          $event->add_record_snapshot('tool_recyclebin_course', $item);
 354          $event->trigger();
 355      }
 356  
 357      /**
 358       * Can we view items in this recycle bin?
 359       *
 360       * @return bool returns true if they can view, false if not
 361       */
 362      public function can_view() {
 363          $context = \context_course::instance($this->_courseid);
 364          return has_capability('tool/recyclebin:viewitems', $context);
 365      }
 366  
 367      /**
 368       * Can we restore items in this recycle bin?
 369       *
 370       * @return bool returns true if they can restore, false if not
 371       */
 372      public function can_restore() {
 373          $context = \context_course::instance($this->_courseid);
 374          return has_capability('tool/recyclebin:restoreitems', $context);
 375      }
 376  
 377      /**
 378       * Can we delete this?
 379       *
 380       * @return bool returns true if they can delete, false if not
 381       */
 382      public function can_delete() {
 383          $context = \context_course::instance($this->_courseid);
 384          return has_capability('tool/recyclebin:deleteitems', $context);
 385      }
 386  }