Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

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