MDL-48012 tool_recyclebin: multiple changes before integration
authorMark Nelson <markn@moodle.com>
Wed, 24 Feb 2016 05:59:06 +0000 (13:59 +0800)
committerMark Nelson <markn@moodle.com>
Fri, 18 Mar 2016 06:12:08 +0000 (14:12 +0800)
- Added new icon for the recycle bin.
- Fixed issue where the course expiry setting was being used to display
  the expiry messages in the category bin.
- Fixed failing Behat tests.
- Tidied Behat tests.
  - General tidy up.
  - Deleted three Behat feature files.
    - Deleted 'delete_confirmation.feature' as it's testing JS functionality
      that was removed (see comment further down). The 'basic_functionality.feature'
      now tests deleting an activity and a course.
    - Deleted 'description.feature' and added the tested functionality to
      'basic_functionality.feature'.
    - Deleted 'logs_test.feature' and moved testing to PHPUnit tests.
  - Added another scenario for restoring a course.
- Removed protected mod settings - it was discussed that this was not a necessary
  feature. We should not be treating the recycle bin as a backup.
- Renamed events to conform to guidelines.
- Wrote new PHPUnit tests for events.
- Tidied up existing PHPUnit tests and extended them.
- Alphabetised lang file.
- Made changes to strings and string identifiers in the lang file and changed usages in code.
- Changed setting names to better identify their purpose.
- Renamed classes and file names so it's easier to identify their purpose.
- Renamed the 'deleted' column to 'timecreated' for consistency with other
  Moodle tables, and to easily identify what the value represents.
- Changed the columns 'category' and 'course' to 'categoryid' and
  'courseid' respectively.
- Changed the name of the table indexes for consistency with core.
- Removed module.js and any use of it and replaced with Moodle core libraries
  for JS confirmation.
- Removed 'noevent' argument in delete_item().
- Removed unnecessary capabilities - we can use the same capabilties for both
  recycle bins based on the context the capability was given.
- Removed '_' in the capability definitions to keep it consistent with core.
- Set page layout and headings for index.php - varies between the course and
  category recycle bins.
- Deleted styles.css and centred columns using the flexible_table API.
- Fixed IDE and codechecker complaints.
- Made use of the files API, rather than writing directly to
  "$CFG->dataroot . '/recyclebin'".
- Used make_temp_directory rather than expecting it to exist.
- Replaced debugging function calls with calls to the backup API to
  display errors.
- Deleted the temporary backup file after a successful restore, or if it fails.
- If the restore fails for a course, delete the course we created.
- Removed unnecessary '\' characters when not in namespace.
- Used configduration class for expiry times in the settings.
- Cleanup course bin when a course is deleted.
- Cleanup a category bin when a category is deleted.
- Removed unnecessary throw tags.
- Changed default settings.

33 files changed:
admin/tool/recyclebin/classes/base_bin.php [moved from admin/tool/recyclebin/classes/recyclebin.php with 66% similarity]
admin/tool/recyclebin/classes/category_bin.php [moved from admin/tool/recyclebin/classes/category.php with 58% similarity]
admin/tool/recyclebin/classes/course_bin.php [moved from admin/tool/recyclebin/classes/course.php with 57% similarity]
admin/tool/recyclebin/classes/event/category_bin_item_created.php [moved from admin/tool/recyclebin/classes/event/course_stored.php with 88% similarity]
admin/tool/recyclebin/classes/event/category_bin_item_deleted.php [moved from admin/tool/recyclebin/classes/event/course_purged.php with 88% similarity]
admin/tool/recyclebin/classes/event/category_bin_item_restored.php [moved from admin/tool/recyclebin/classes/event/course_restored.php with 88% similarity]
admin/tool/recyclebin/classes/event/course_bin_item_created.php [moved from admin/tool/recyclebin/classes/event/item_stored.php with 88% similarity]
admin/tool/recyclebin/classes/event/course_bin_item_deleted.php [moved from admin/tool/recyclebin/classes/event/item_purged.php with 88% similarity]
admin/tool/recyclebin/classes/event/course_bin_item_restored.php [moved from admin/tool/recyclebin/classes/event/item_restored.php with 88% similarity]
admin/tool/recyclebin/classes/task/cleanup_category_bin.php [moved from admin/tool/recyclebin/classes/task/cleanup_courses.php with 69% similarity]
admin/tool/recyclebin/classes/task/cleanup_course_bin.php [moved from admin/tool/recyclebin/classes/task/cleanup_activities.php with 59% similarity]
admin/tool/recyclebin/db/access.php
admin/tool/recyclebin/db/install.xml
admin/tool/recyclebin/db/tasks.php
admin/tool/recyclebin/index.php
admin/tool/recyclebin/lang/en/tool_recyclebin.php
admin/tool/recyclebin/lib.php
admin/tool/recyclebin/module.js [deleted file]
admin/tool/recyclebin/pix/trash.png [new file with mode: 0644]
admin/tool/recyclebin/pix/trash.svg [new file with mode: 0644]
admin/tool/recyclebin/settings.php
admin/tool/recyclebin/styles.css [deleted file]
admin/tool/recyclebin/tests/behat/backup_user_data.feature
admin/tool/recyclebin/tests/behat/basic_functionality.feature
admin/tool/recyclebin/tests/behat/delete_confirmation.feature [deleted file]
admin/tool/recyclebin/tests/behat/description.feature [deleted file]
admin/tool/recyclebin/tests/behat/logs_test.feature [deleted file]
admin/tool/recyclebin/tests/category_bin_test.php [new file with mode: 0644]
admin/tool/recyclebin/tests/category_test.php [deleted file]
admin/tool/recyclebin/tests/course_bin_test.php [new file with mode: 0644]
admin/tool/recyclebin/tests/course_test.php [deleted file]
admin/tool/recyclebin/tests/events_test.php [new file with mode: 0644]
admin/tool/recyclebin/version.php

similarity index 66%
rename from admin/tool/recyclebin/classes/recyclebin.php
rename to admin/tool/recyclebin/classes/base_bin.php
index f32941a..84c9e84 100644 (file)
@@ -33,8 +33,8 @@ defined('MOODLE_INTERNAL') || die();
  * @copyright  2015 University of Kent
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-abstract class recyclebin
-{
+abstract class base_bin {
+
     /**
      * Is this recyclebin enabled?
      */
@@ -45,7 +45,7 @@ abstract class recyclebin
     /**
      * Returns an item from the recycle bin.
      *
-     * @param $item int Item ID to retrieve.
+     * @param int $itemid Item ID to retrieve.
      */
     public abstract function get_item($itemid);
 
@@ -57,32 +57,23 @@ abstract class recyclebin
     /**
      * Store an item in this recycle bin.
      *
-     * @param $item stdClass Item to store.
-     * @throws \coding_exception
-     * @throws \invalid_dataroot_permissions
-     * @throws \moodle_exception
+     * @param \stdClass $item Item to store.
      */
     public abstract function store_item($item);
 
     /**
      * Restore an item from the recycle bin.
      *
-     * @param stdClass $item The item database record
-     * @throws \Exception
-     * @throws \coding_exception
-     * @throws \moodle_exception
-     * @throws \restore_controller_exception
+     * @param \stdClass $item The item database record
      */
     public abstract function restore_item($item);
 
     /**
      * Delete an item from the recycle bin.
      *
-     * @param stdClass $item The item database record
-     * @param boolean $noevent Whether or not to fire a purged event.
-     * @throws \coding_exception
+     * @param \stdClass $item The item database record
      */
-    public abstract function delete_item($item, $noevent = false);
+    public abstract function delete_item($item);
 
     /**
      * Empty the recycle bin.
@@ -91,30 +82,24 @@ abstract class recyclebin
         // Cleanup all items.
         $items = $this->get_items();
         foreach ($items as $item) {
-            if ($this->can_delete($item)) {
+            if ($this->can_delete()) {
                 $this->delete_item($item);
             }
         }
     }
 
     /**
-     * Can we view this?
-     *
-     * @param stdClass $item The item database record
+     * Can we view items in this recycle bin?
      */
-    public abstract function can_view($item);
+    public abstract function can_view();
 
     /**
-     * Can we restore this?
-     *
-     * @param stdClass $item The item database record
+     * Can we restore items in this recycle bin?
      */
-    public abstract function can_restore($item);
+    public abstract function can_restore();
 
     /**
      * Can we delete this?
-     *
-     * @param stdClass $item The item database record
      */
-    public abstract function can_delete($item);
+    public abstract function can_delete();
 }
similarity index 58%
rename from admin/tool/recyclebin/classes/category.php
rename to admin/tool/recyclebin/classes/category_bin.php
index 09c9b08..06b06de 100644 (file)
@@ -26,6 +26,8 @@ namespace tool_recyclebin;
 
 defined('MOODLE_INTERNAL') || die();
 
+define('TOOL_RECYCLEBIN_COURSECAT_BIN_FILEAREA', 'recyclebin_coursecat');
+
 /**
  * Represents a category's recyclebin.
  *
@@ -33,14 +35,17 @@ defined('MOODLE_INTERNAL') || die();
  * @copyright  2015 University of Kent
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-class category extends recyclebin
-{
-    private $_categoryid;
+class category_bin extends base_bin {
+
+    /**
+     * @var int The category id.
+     */
+    protected $_categoryid;
 
     /**
      * Constructor.
      *
-     * @param int $categoryid Category ID.
+     * @param int $categoryid The category id.
      */
     public function __construct($categoryid) {
         $this->_categoryid = $categoryid;
@@ -48,15 +53,18 @@ class category extends recyclebin
 
     /**
      * Is this recyclebin enabled?
+     *
+     * @return bool true if enabled, false if not.
      */
     public static function is_enabled() {
-        return get_config('tool_recyclebin', 'enablecategory');
+        return get_config('tool_recyclebin', 'categorybinenable');
     }
 
     /**
      * Returns an item from the recycle bin.
      *
-     * @param $item int Item ID to retrieve.
+     * @param int $itemid Item ID to retrieve.
+     * @return \stdClass the item.
      */
     public function get_item($itemid) {
         global $DB;
@@ -72,12 +80,14 @@ class category extends recyclebin
 
     /**
      * Returns a list of items in the recycle bin for this course.
+     *
+     * @return array the list of items.
      */
     public function get_items() {
         global $DB;
 
         $items = $DB->get_records('tool_recyclebin_category', array(
-            'category' => $this->_categoryid
+            'categoryid' => $this->_categoryid
         ));
 
         foreach ($items as $item) {
@@ -90,9 +100,7 @@ class category extends recyclebin
     /**
      * Store a course in the recycle bin.
      *
-     * @param $course stdClass Course
-     * @throws \coding_exception
-     * @throws \invalid_dataroot_permissions
+     * @param \stdClass $course Course
      * @throws \moodle_exception
      */
     public function store_item($course) {
@@ -124,22 +132,26 @@ class category extends recyclebin
             throw new \moodle_exception('Failed to backup activity prior to deletion (invalid file).');
         }
 
-        // Make sure our backup dir exists.
-        $bindir = $CFG->dataroot . '/recyclebin';
-        if (!file_exists($bindir)) {
-            make_writable_directory($bindir);
-        }
-
         // Record the activity, get an ID.
-        $binid = $DB->insert_record('tool_recyclebin_category', array(
-            'category' => $course->category,
-            'shortname' => $course->shortname,
-            'fullname' => $course->fullname,
-            'deleted' => time()
-        ));
+        $item = new \stdClass();
+        $item->categoryid = $course->category;
+        $item->shortname = $course->shortname;
+        $item->fullname = $course->fullname;
+        $item->timecreated = time();
+        $binid = $DB->insert_record('tool_recyclebin_category', $item);
+
+        // Create the location we want to copy this file to.
+        $filerecord = array(
+            'contextid' => \context_coursecat::instance($course->category)->id,
+            'component' => 'tool_recyclebin',
+            'filearea' => TOOL_RECYCLEBIN_COURSECAT_BIN_FILEAREA,
+            'itemid' => $binid,
+            'timemodified' => time()
+        );
 
         // Move the file to our own special little place.
-        if (!$file->copy_content_to($bindir . '/course-' . $binid)) {
+        $fs = get_file_storage();
+        if (!$fs->create_file_from_storedfile($filerecord, $file)) {
             // Failed, cleanup first.
             $DB->delete_records('tool_recyclebin_category', array(
                 'id' => $binid
@@ -152,7 +164,7 @@ class category extends recyclebin
         $file->delete();
 
         // Fire event.
-        $event = \tool_recyclebin\event\course_stored::create(array(
+        $event = \tool_recyclebin\event\category_bin_item_created::create(array(
             'objectid' => $binid,
             'context' => \context_coursecat::instance($course->category)
         ));
@@ -162,20 +174,44 @@ class category extends recyclebin
     /**
      * Restore an item from the recycle bin.
      *
-     * @param stdClass $item The item database record
-     * @throws \Exception
-     * @throws \coding_exception
+     * @param \stdClass $item The item database record
      * @throws \moodle_exception
-     * @throws \restore_controller_exception
      */
     public function restore_item($item) {
-        global $CFG;
+        global $CFG, $OUTPUT, $PAGE;
 
         require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
         require_once($CFG->dirroot . '/course/lib.php');
 
         $user = get_admin();
 
+        // Grab the course category context.
+        $context = \context_coursecat::instance($this->_categoryid);
+
+        // Get the backup file.
+        $fs = get_file_storage();
+        $files = $fs->get_area_files($context->id, 'tool_recyclebin', TOOL_RECYCLEBIN_COURSECAT_BIN_FILEAREA, $item->id,
+            'itemid, filepath, filename', false);
+
+        if (empty($files)) {
+            throw new \moodle_exception('Invalid recycle bin item!');
+        }
+
+        if (count($files) > 1) {
+            throw new \moodle_exception('Too many files found!');
+        }
+
+        // Get the backup file.
+        $file = reset($files);
+
+        // Get a temp directory name and create it.
+        $tempdir = \restore_controller::get_tempdir_name($context->id, $user->id);
+        $fulltempdir = make_temp_directory('/backup/' . $tempdir);
+
+        // Extract the backup to tmpdir.
+        $fb = get_file_packer('application/vnd.moodle.backup');
+        $fb->extract_to_pathname($file, $fulltempdir);
+
         // Build a course.
         $course = new \stdClass();
         $course->category = $this->_categoryid;
@@ -183,33 +219,15 @@ class category extends recyclebin
         $course->fullname = $item->fullname;
         $course->summary = '';
 
-        // TODO - Maybe handle non-unique shortnames, missing categories, etc?
-
         // Create a new course.
         $course = create_course($course);
         if (!$course) {
             throw new \moodle_exception("Could not create course to restore into.");
         }
 
-        // Get the pathname.
-        $source = $CFG->dataroot . '/recyclebin/course-' . $item->id;
-        if (!file_exists($source)) {
-            throw new \moodle_exception('Invalid recycle bin item!');
-        }
-
-        // Grab the course context.
-        $context = \context_coursecat::instance($this->_categoryid);
-
-        // Grab a tmpdir.
-        $tmpdir = \restore_controller::get_tempdir_name($context->id, $user->id);
-
-        // Extract the backup to tmpdir.
-        $fb = get_file_packer('application/vnd.moodle.backup');
-        $fb->extract_to_pathname($source, $CFG->tempdir . '/backup/' . $tmpdir . '/');
-
         // Define the import.
         $controller = new \restore_controller(
-            $tmpdir,
+            $tempdir,
             $course->id,
             \backup::INTERACTIVE_NO,
             \backup::MODE_GENERAL,
@@ -221,13 +239,20 @@ class category extends recyclebin
         if (!$controller->execute_precheck()) {
             $results = $controller->get_precheck_results();
 
-            if (isset($results['errors'])) {
-                debugging(var_export($results, true));
-                throw new \moodle_exception("Restore failed.");
-            }
+            // Check if errors have been found.
+            if (!empty($results['errors'])) {
+                // Delete the temporary file we created.
+                fulldelete($fulltempdir);
 
-            if (isset($results['warnings'])) {
-                debugging(var_export($results['warnings'], true));
+                // Delete the course we created.
+                delete_course($course, false);
+
+                echo $OUTPUT->header();
+                $backuprenderer = $PAGE->get_renderer('core', 'backup');
+                echo $backuprenderer->precheck_notices($results);
+                echo $OUTPUT->continue_button(new \moodle_url('/course/index.php', array('categoryid' => $this->_categoryid)));
+                echo $OUTPUT->footer();
+                exit();
             }
         }
 
@@ -235,7 +260,7 @@ class category extends recyclebin
         $controller->execute_plan();
 
         // Fire event.
-        $event = \tool_recyclebin\event\course_restored::create(array(
+        $event = \tool_recyclebin\event\category_bin_item_restored::create(array(
             'objectid' => $item->id,
             'context' => $context
         ));
@@ -243,67 +268,70 @@ class category extends recyclebin
         $event->trigger();
 
         // Cleanup.
-        $this->delete_item($item, true);
+        fulldelete($fulltempdir);
+        $this->delete_item($item);
     }
 
     /**
      * Delete an item from the recycle bin.
      *
-     * @param stdClass $item The item database record
-     * @param boolean $noevent Whether or not to fire a purged event.
+     * @param \stdClass $item The item database record
      * @throws \coding_exception
      */
-    public function delete_item($item, $noevent = false) {
-        global $CFG, $DB;
+    public function delete_item($item) {
+        global $DB;
 
-        // Delete the file.
-        unlink($CFG->dataroot . '/recyclebin/course-' . $item->id);
+        // Grab the course category context.
+        $context = \context_coursecat::instance($this->_categoryid);
+
+        // Delete the files.
+        $fs = get_file_storage();
+        $files = $fs->get_area_files($context->id, 'tool_recyclebin', TOOL_RECYCLEBIN_COURSECAT_BIN_FILEAREA, $item->id);
+        foreach ($files as $file) {
+            $file->delete();
+        }
 
         // Delete the record.
         $DB->delete_records('tool_recyclebin_category', array(
             'id' => $item->id
         ));
 
-        if ($noevent) {
-            return;
-        }
-
         // Fire event.
-        $event = \tool_recyclebin\event\course_purged::create(array(
+        $event = \tool_recyclebin\event\category_bin_item_deleted::create(array(
             'objectid' => $item->id,
-            'context' => \context_coursecat::instance($item->category)
+            'context' => \context_coursecat::instance($item->categoryid)
         ));
         $event->add_record_snapshot('tool_recyclebin_category', $item);
         $event->trigger();
     }
 
     /**
-     * Can we view this item?
+     * Can we view items in this recycle bin?
      *
-     * @param stdClass $item The item database record
+     * @return bool returns true if they can view, false if not
      */
-    public function can_view($item) {
-        $context = \context_coursecat::instance($item->category);
-        return has_capability('tool/recyclebin:view_course', $context);
+    public function can_view() {
+        $context = \context_coursecat::instance($this->_categoryid);
+        return has_capability('tool/recyclebin:viewitems', $context);
     }
 
     /**
-     * Can we restore this?
+     * Can we restore items in this recycle bin?
      *
-     * @param stdClass $item The item database record
+     * @return bool returns true if they can restore, false if not
      */
-    public function can_restore($item) {
-        $context = \context_coursecat::instance($item->category);
-        return has_capability('tool/recyclebin:restore_course', $context);
+    public function can_restore() {
+        $context = \context_coursecat::instance($this->_categoryid);
+        return has_capability('tool/recyclebin:restoreitems', $context);
     }
 
     /**
-     * Can we delete this?
+     * Can we delete items in this recycle bin?
      *
-     * @param stdClass $item The item database record
+     * @return bool returns true if they can delete, false if not
      */
-    public function can_delete($item) {
-        $context = \context_coursecat::instance($item->category);
-        return has_capability('tool/recyclebin:delete_course', $context);
+    public function can_delete() {
+        $context = \context_coursecat::instance($this->_categoryid);
+        return has_capability('tool/recyclebin:deleteitems', $context);
     }
 }
similarity index 57%
rename from admin/tool/recyclebin/classes/course.php
rename to admin/tool/recyclebin/classes/course_bin.php
index 17c9cf0..a05a7cf 100644 (file)
@@ -26,6 +26,8 @@ namespace tool_recyclebin;
 
 defined('MOODLE_INTERNAL') || die();
 
+define('TOOL_RECYCLEBIN_COURSE_BIN_FILEAREA', 'recyclebin_course');
+
 /**
  * Represents a course's recyclebin.
  *
@@ -33,9 +35,12 @@ defined('MOODLE_INTERNAL') || die();
  * @copyright  2015 University of Kent
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-class course extends recyclebin
-{
-    private $_courseid;
+class course_bin extends base_bin {
+
+    /**
+     * @var int The course id.
+     */
+    protected $_courseid;
 
     /**
      * Constructor.
@@ -48,15 +53,18 @@ class course extends recyclebin
 
     /**
      * Is this recyclebin enabled?
+     *
+     * @return bool true if enabled, false if not.
      */
     public static function is_enabled() {
-        return get_config('tool_recyclebin', 'enablecourse');
+        return get_config('tool_recyclebin', 'coursebinenable');
     }
 
     /**
      * Returns an item from the recycle bin.
      *
-     * @param $item int Item ID to retrieve.
+     * @param int $itemid Item ID to retrieve.
+     * @return \stdClass the item.
      */
     public function get_item($itemid) {
         global $DB;
@@ -68,21 +76,21 @@ class course extends recyclebin
 
     /**
      * Returns a list of items in the recycle bin for this course.
+     *
+     * @return array the list of items.
      */
     public function get_items() {
         global $DB;
 
         return $DB->get_records('tool_recyclebin_course', array(
-            'course' => $this->_courseid
+            'courseid' => $this->_courseid
         ));
     }
 
     /**
      * Store a course module in the recycle bin.
      *
-     * @param $cm stdClass Course module
-     * @throws \coding_exception
-     * @throws \invalid_dataroot_permissions
+     * @param \stdClass $cm Course module
      * @throws \moodle_exception
      */
     public function store_item($cm) {
@@ -128,23 +136,27 @@ class course extends recyclebin
             throw new \moodle_exception('Failed to backup activity prior to deletion (invalid file).');
         }
 
-        // Make sure our backup dir exists.
-        $bindir = $CFG->dataroot . '/recyclebin';
-        if (!file_exists($bindir)) {
-            make_writable_directory($bindir);
-        }
-
         // Record the activity, get an ID.
-        $binid = $DB->insert_record('tool_recyclebin_course', array(
-            'course' => $cm->course,
-            'section' => $cm->section,
-            'module' => $cm->module,
-            'name' => $cminfo->name,
-            'deleted' => time()
-        ));
+        $activity = new \stdClass();
+        $activity->courseid = $cm->course;
+        $activity->section = $cm->section;
+        $activity->module = $cm->module;
+        $activity->name = $cminfo->name;
+        $activity->timecreated = time();
+        $binid = $DB->insert_record('tool_recyclebin_course', $activity);
+
+        // Create the location we want to copy this file to.
+        $filerecord = array(
+            'contextid' => \context_course::instance($this->_courseid)->id,
+            'component' => 'tool_recyclebin',
+            'filearea' => TOOL_RECYCLEBIN_COURSE_BIN_FILEAREA,
+            'itemid' => $binid,
+            'timemodified' => time()
+        );
 
         // Move the file to our own special little place.
-        if (!$file->copy_content_to($bindir . '/' . $binid)) {
+        $fs = get_file_storage();
+        if (!$fs->create_file_from_storedfile($filerecord, $file)) {
             // Failed, cleanup first.
             $DB->delete_records('tool_recyclebin_course', array(
                 'id' => $binid
@@ -157,7 +169,7 @@ class course extends recyclebin
         $file->delete();
 
         // Fire event.
-        $event = \tool_recyclebin\event\item_stored::create(array(
+        $event = \tool_recyclebin\event\course_bin_item_created::create(array(
             'objectid' => $binid,
             'context' => \context_course::instance($cm->course)
         ));
@@ -167,38 +179,46 @@ class course extends recyclebin
     /**
      * Restore an item from the recycle bin.
      *
-     * @param stdClass $item The item database record
-     * @throws \Exception
-     * @throws \coding_exception
+     * @param \stdClass $item The item database record
      * @throws \moodle_exception
-     * @throws \restore_controller_exception
      */
     public function restore_item($item) {
-        global $CFG;
+        global $CFG, $OUTPUT, $PAGE;
 
         require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
 
         $user = get_admin();
 
-        // Get the pathname.
-        $source = $CFG->dataroot . '/recyclebin/' . $item->id;
-        if (!file_exists($source)) {
+        // Grab the course context.
+        $context = \context_course::instance($this->_courseid);
+
+        // Get the files..
+        $fs = get_file_storage();
+        $files = $fs->get_area_files($context->id, 'tool_recyclebin', TOOL_RECYCLEBIN_COURSE_BIN_FILEAREA, $item->id,
+            'itemid, filepath, filename', false);
+
+        if (empty($files)) {
             throw new \moodle_exception('Invalid recycle bin item!');
         }
 
-        // Grab the course context.
-        $context = \context_course::instance($this->_courseid);
+        if (count($files) > 1) {
+            throw new \moodle_exception('Too many files found!');
+        }
+
+        // Get the backup file.
+        $file = reset($files);
 
-        // Grab a tmpdir.
-        $tmpdir = \restore_controller::get_tempdir_name($context->id, $user->id);
+        // Get a temp directory name and create it.
+        $tempdir = \restore_controller::get_tempdir_name($context->id, $user->id);
+        $fulltempdir = make_temp_directory('/backup/' . $tempdir);
 
-        // Extract the backup to tmpdir.
+        // Extract the backup to tempdir.
         $fb = get_file_packer('application/vnd.moodle.backup');
-        $fb->extract_to_pathname($source, $CFG->tempdir . '/backup/' . $tmpdir . '/');
+        $fb->extract_to_pathname($file, $fulltempdir);
 
         // Define the import.
         $controller = new \restore_controller(
-            $tmpdir,
+            $tempdir,
             $this->_courseid,
             \backup::INTERACTIVE_NO,
             \backup::MODE_GENERAL,
@@ -210,13 +230,16 @@ class course extends recyclebin
         if (!$controller->execute_precheck()) {
             $results = $controller->get_precheck_results();
 
-            if (isset($results['errors'])) {
-                debugging(var_export($results, true));
-                throw new \moodle_exception("Restore failed.");
-            }
+            // If errors are found then delete the file we created.
+            if (!empty($results['errors'])) {
+                fulldelete($fulltempdir);
 
-            if (isset($results['warnings'])) {
-                debugging(var_export($results['warnings'], true));
+                echo $OUTPUT->header();
+                $backuprenderer = $PAGE->get_renderer('core', 'backup');
+                echo $backuprenderer->precheck_notices($results);
+                echo $OUTPUT->continue_button(new \moodle_url('/course/view.php', array('id' => $this->_courseid)));
+                echo $OUTPUT->footer();
+                exit();
             }
         }
 
@@ -224,7 +247,7 @@ class course extends recyclebin
         $controller->execute_plan();
 
         // Fire event.
-        $event = \tool_recyclebin\event\item_restored::create(array(
+        $event = \tool_recyclebin\event\course_bin_item_restored::create(array(
             'objectid' => $item->id,
             'context' => $context
         ));
@@ -232,40 +255,41 @@ class course extends recyclebin
         $event->trigger();
 
         // Cleanup.
-        $this->delete_item($item, true);
+        fulldelete($fulltempdir);
+        $this->delete_item($item);
     }
 
     /**
      * Delete an item from the recycle bin.
      *
-     * @param stdClass $item The item database record
-     * @param boolean $noevent Whether or not to fire a purged event.
-     * @throws \coding_exception
+     * @param \stdClass $item The item database record
      */
-    public function delete_item($item, $noevent = false) {
-        global $CFG, $DB;
+    public function delete_item($item) {
+        global $DB;
 
-        // Delete the file.
-        unlink($CFG->dataroot . '/recyclebin/' . $item->id);
+        // Grab the course context.
+        $context = \context_course::instance($this->_courseid);
+
+        // Delete the files.
+        $fs = get_file_storage();
+        $files = $fs->get_area_files($context->id, 'tool_recyclebin', TOOL_RECYCLEBIN_COURSE_BIN_FILEAREA, $item->id);
+        foreach ($files as $file) {
+            $file->delete();
+        }
 
         // Delete the record.
         $DB->delete_records('tool_recyclebin_course', array(
             'id' => $item->id
         ));
 
-        // Return now if we don't need an event.
-        if ($noevent) {
-            return;
-        }
-
         // The course might have been deleted, check we have a context.
-        $context = \context_course::instance($item->course, \IGNORE_MISSING);
+        $context = \context_course::instance($item->courseid, \IGNORE_MISSING);
         if (!$context) {
             return;
         }
 
         // Fire event.
-        $event = \tool_recyclebin\event\item_purged::create(array(
+        $event = \tool_recyclebin\event\course_bin_item_deleted::create(array(
             'objectid' => $item->id,
             'context' => $context
         ));
@@ -274,46 +298,32 @@ class course extends recyclebin
     }
 
     /**
-     * Can we view this item?
+     * Can we view items in this recycle bin?
      *
-     * @param stdClass $item The item database record
+     * @return bool returns true if they can view, false if not
      */
-    public function can_view($item) {
-        $context = \context_course::instance($item->course);
-        return has_capability('tool/recyclebin:view_item', $context);
+    public function can_view() {
+        $context = \context_course::instance($this->_courseid);
+        return has_capability('tool/recyclebin:viewitems', $context);
     }
 
     /**
-     * Can we restore this?
+     * Can we restore items in this recycle bin?
      *
-     * @param stdClass $item The item database record
+     * @return bool returns true if they can restore, false if not
      */
-    public function can_restore($item) {
-        $context = \context_course::instance($item->course);
-        return has_capability('tool/recyclebin:restore_item', $context);
+    public function can_restore() {
+        $context = \context_course::instance($this->_courseid);
+        return has_capability('tool/recyclebin:restoreitems', $context);
     }
 
     /**
      * Can we delete this?
      *
-     * @param stdClass $item The item database record
+     * @return bool returns true if they can delete, false if not
      */
-    public function can_delete($item) {
-        $context = \context_course::instance($item->course);
-
-        // Basic check - do we have the first require capability?
-        if (!has_capability('tool/recyclebin:delete_item', $context)) {
-            return false;
-        }
-
-        // Are we a protected item?
-        $protected = get_config('tool_recyclebin', 'protectedmods');
-        $protected = explode(',', $protected);
-        if (!in_array($item->module, $protected)) {
-            return true;
-        }
-
-        // Yes! Can we delete protected items?
-        return has_capability('tool/recyclebin:delete_protected_item', $context);
+    public function can_delete() {
+        $context = \context_course::instance($this->_courseid);
+        return has_capability('tool/recyclebin:deleteitems', $context);
     }
 }
@@ -27,14 +27,14 @@ namespace tool_recyclebin\event;
 defined('MOODLE_INTERNAL') || die();
 
 /**
- * Event Class
+ * Event class.
  *
  * @package    tool_recyclebin
  * @copyright  2015 University of Kent
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-class course_stored extends \core\event\base
-{
+class category_bin_item_created extends \core\event\base {
+
     /**
      * Init method.
      */
@@ -50,7 +50,7 @@ class course_stored extends \core\event\base
      * @return string
      */
     public static function get_name() {
-        return get_string('event_stored_name', 'tool_recyclebin');
+        return get_string('eventitemcreated', 'tool_recyclebin');
     }
 
     /**
@@ -59,7 +59,7 @@ class course_stored extends \core\event\base
      * @return string
      */
     public function get_description() {
-        return get_string('event_stored_description', 'tool_recyclebin', array(
+        return get_string('eventitemcreated_desc', 'tool_recyclebin', array(
             'objectid' => $this->objectid
         ));
     }
@@ -27,14 +27,14 @@ namespace tool_recyclebin\event;
 defined('MOODLE_INTERNAL') || die();
 
 /**
- * Event Class
+ * Event class.
  *
  * @package    tool_recyclebin
  * @copyright  2015 University of Kent
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-class course_purged extends \core\event\base
-{
+class category_bin_item_deleted extends \core\event\base {
+
     /**
      * Init method.
      */
@@ -50,7 +50,7 @@ class course_purged extends \core\event\base
      * @return string
      */
     public static function get_name() {
-        return get_string('event_purged_name', 'tool_recyclebin');
+        return get_string('eventitemdeleted', 'tool_recyclebin');
     }
 
     /**
@@ -59,7 +59,7 @@ class course_purged extends \core\event\base
      * @return string
      */
     public function get_description() {
-        return get_string('event_purged_description', 'tool_recyclebin', array(
+        return get_string('eventitemdeleted_desc', 'tool_recyclebin', array(
             'objectid' => $this->objectid
         ));
     }
@@ -33,8 +33,8 @@ defined('MOODLE_INTERNAL') || die();
  * @copyright  2015 University of Kent
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-class course_restored extends \core\event\base
-{
+class category_bin_item_restored extends \core\event\base {
+
     /**
      * Init method.
      */
@@ -50,7 +50,7 @@ class course_restored extends \core\event\base
      * @return string
      */
     public static function get_name() {
-        return get_string('event_restored_name', 'tool_recyclebin');
+        return get_string('eventitemrestored', 'tool_recyclebin');
     }
 
     /**
@@ -59,7 +59,7 @@ class course_restored extends \core\event\base
      * @return string
      */
     public function get_description() {
-        return get_string('event_restored_description', 'tool_recyclebin', array(
+        return get_string('eventitemrestored_desc', 'tool_recyclebin', array(
             'objectid' => $this->objectid
         ));
     }
@@ -27,14 +27,14 @@ namespace tool_recyclebin\event;
 defined('MOODLE_INTERNAL') || die();
 
 /**
- * Event Class
+ * Event class.
  *
  * @package    tool_recyclebin
  * @copyright  2015 University of Kent
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-class item_stored extends \core\event\base
-{
+class course_bin_item_created extends \core\event\base {
+
     /**
      * Init method.
      */
@@ -50,7 +50,7 @@ class item_stored extends \core\event\base
      * @return string
      */
     public static function get_name() {
-        return get_string('event_stored_name', 'tool_recyclebin');
+        return get_string('eventitemcreated', 'tool_recyclebin');
     }
 
     /**
@@ -59,7 +59,7 @@ class item_stored extends \core\event\base
      * @return string
      */
     public function get_description() {
-        return get_string('event_stored_description', 'tool_recyclebin', array(
+        return get_string('eventitemcreated_desc', 'tool_recyclebin', array(
             'objectid' => $this->objectid
         ));
     }
@@ -27,14 +27,14 @@ namespace tool_recyclebin\event;
 defined('MOODLE_INTERNAL') || die();
 
 /**
- * Event Class
+ * Event class.
  *
  * @package    tool_recyclebin
  * @copyright  2015 University of Kent
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-class item_purged extends \core\event\base
-{
+class course_bin_item_deleted extends \core\event\base {
+
     /**
      * Init method.
      */
@@ -50,7 +50,7 @@ class item_purged extends \core\event\base
      * @return string
      */
     public static function get_name() {
-        return get_string('event_purged_name', 'tool_recyclebin');
+        return get_string('eventitemdeleted', 'tool_recyclebin');
     }
 
     /**
@@ -59,7 +59,7 @@ class item_purged extends \core\event\base
      * @return string
      */
     public function get_description() {
-        return get_string('event_purged_description', 'tool_recyclebin', array(
+        return get_string('eventitemdeleted_desc', 'tool_recyclebin', array(
             'objectid' => $this->objectid
         ));
     }
@@ -27,14 +27,14 @@ namespace tool_recyclebin\event;
 defined('MOODLE_INTERNAL') || die();
 
 /**
- * Event Class
+ * Event class.
  *
  * @package    tool_recyclebin
  * @copyright  2015 University of Kent
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-class item_restored extends \core\event\base
-{
+class course_bin_item_restored extends \core\event\base {
+
     /**
      * Init method.
      */
@@ -50,7 +50,7 @@ class item_restored extends \core\event\base
      * @return string
      */
     public static function get_name() {
-        return get_string('event_restored_name', 'tool_recyclebin');
+        return get_string('eventitemrestored', 'tool_recyclebin');
     }
 
     /**
@@ -59,7 +59,7 @@ class item_restored extends \core\event\base
      * @return string
      */
     public function get_description() {
-        return get_string('event_restored_description', 'tool_recyclebin', array(
+        return get_string('eventitemrestored_desc', 'tool_recyclebin', array(
             'objectid' => $this->objectid
         ));
     }
@@ -31,12 +31,13 @@ namespace tool_recyclebin\task;
  * @copyright  2015 University of Kent
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-class cleanup_courses extends \core\task\scheduled_task {
+class cleanup_category_bin extends \core\task\scheduled_task {
+
     /**
      * Task name.
      */
     public function get_name() {
-        return get_string('cleancategoryrecyclebin', 'tool_recyclebin');
+        return get_string('taskcleanupcategorybin', 'tool_recyclebin');
     }
 
     /**
@@ -45,17 +46,18 @@ class cleanup_courses extends \core\task\scheduled_task {
     public function execute() {
         global $DB;
 
-        // Delete courses.
-        $lifetime = get_config('tool_recyclebin', 'course_expiry');
-        if (!\tool_recyclebin\category::is_enabled() || $lifetime <= 0) {
+        // Check if the category bin is disabled or there is no expiry time.
+        $lifetime = get_config('tool_recyclebin', 'categorybinexpiry');
+        if (!\tool_recyclebin\category_bin::is_enabled() || $lifetime <= 0) {
             return true;
         }
 
-        $items = $DB->get_recordset_select('tool_recyclebin_category', 'deleted < ?', array(time() - (86400 * $lifetime)));
+        // Get the items we can delete.
+        $items = $DB->get_recordset_select('tool_recyclebin_category', 'timecreated <= :timecreated',
+            array('timecreated' => time() - $lifetime));
         foreach ($items as $item) {
-            mtrace("[RecycleBin] Deleting course {$item->id}...");
-
-            $bin = new \tool_recyclebin\category($item->category);
+            mtrace("[tool_recyclebin] Deleting item '{$item->id}' from the category recycle bin ...");
+            $bin = new \tool_recyclebin\category_bin($item->categoryid);
             $bin->delete_item($item);
         }
         $items->close();
@@ -31,12 +31,13 @@ namespace tool_recyclebin\task;
  * @copyright  2015 University of Kent
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-class cleanup_activities extends \core\task\scheduled_task {
+class cleanup_course_bin extends \core\task\scheduled_task {
+
     /**
      * Task name.
      */
     public function get_name() {
-        return get_string('cleancourserecyclebin', 'tool_recyclebin');
+        return get_string('taskcleanupcoursebin', 'tool_recyclebin');
     }
 
     /**
@@ -45,34 +46,18 @@ class cleanup_activities extends \core\task\scheduled_task {
     public function execute() {
         global $DB;
 
-        // Delete mods.
-        $lifetime = get_config('tool_recyclebin', 'expiry');
-        if (!\tool_recyclebin\course::is_enabled() || $lifetime <= 0) {
+        // Check if the course bin is disabled or there is no expiry time.
+        $lifetime = get_config('tool_recyclebin', 'coursebinexpiry');
+        if (!\tool_recyclebin\course_bin::is_enabled() || $lifetime <= 0) {
             return true;
         }
 
-        // Start building SQL.
-        $sql = '';
-        $params = array();
-
-        // Protected mods are exempt.
-        $protected = get_config('tool_recyclebin', 'protectedmods');
-        if (!empty($protected)) {
-            $protected = explode(',', $protected);
-            list($sql, $params) = $DB->get_in_or_equal($protected, SQL_PARAMS_NAMED, 'm', false);
-            $sql = " AND module {$sql}";
-        }
-
-        // Add deleted param.
-        $params = is_array($params) ? $params : array();
-        $params['deleted'] = time() - (86400 * $lifetime);
-
-        // Delete items.
-        $items = $DB->get_recordset_select('tool_recyclebin_course', 'deleted < :deleted' . $sql, $params);
+        // Get the items we can delete.
+        $items = $DB->get_recordset_select('tool_recyclebin_course', 'timecreated <= :timecreated',
+            array('timecreated' => time() - $lifetime));
         foreach ($items as $item) {
-            mtrace("[RecycleBin] Deleting item {$item->id}...");
-
-            $bin = new \tool_recyclebin\course($item->course);
+            mtrace("[tool_recyclebin] Deleting item '{$item->id}' from the course recycle bin ...");
+            $bin = new \tool_recyclebin\course_bin($item->courseid);
             $bin->delete_item($item);
         }
         $items->close();
index f359e1d..b300bfb 100644 (file)
 defined('MOODLE_INTERNAL') || die();
 
 $capabilities = array(
-    'tool/recyclebin:view_item' => array(
-        'captype' => 'read',
-        'contextlevel' => CONTEXT_COURSE,
-        'archetypes' => array(
-            'teacher' => CAP_ALLOW,
-            'editingteacher' => CAP_ALLOW,
-            'manager' => CAP_ALLOW
-        )
-    ),
 
-    'tool/recyclebin:restore_item' => array(
+    'tool/recyclebin:deleteitems' => array(
         'captype' => 'write',
+        'riskbitmask' => RISK_DATALOSS,
         'contextlevel' => CONTEXT_COURSE,
         'archetypes' => array(
             'editingteacher' => CAP_ALLOW,
@@ -44,9 +36,8 @@ $capabilities = array(
         )
     ),
 
-    'tool/recyclebin:delete_item' => array(
+    'tool/recyclebin:restoreitems' => array(
         'captype' => 'write',
-        'riskbitmask' => RISK_DATALOSS,
         'contextlevel' => CONTEXT_COURSE,
         'archetypes' => array(
             'editingteacher' => CAP_ALLOW,
@@ -54,36 +45,12 @@ $capabilities = array(
         )
     ),
 
-    'tool/recyclebin:view_course' => array(
+    'tool/recyclebin:viewitems' => array(
         'captype' => 'read',
-        'contextlevel' => CONTEXT_COURSECAT,
-        'archetypes' => array(
-            'manager' => CAP_ALLOW
-        )
-    ),
-
-    'tool/recyclebin:restore_course' => array(
-        'captype' => 'write',
-        'contextlevel' => CONTEXT_COURSECAT,
-        'archetypes' => array(
-            'manager' => CAP_ALLOW
-        )
-    ),
-
-    'tool/recyclebin:delete_course' => array(
-        'captype' => 'write',
-        'riskbitmask' => RISK_DATALOSS,
-        'contextlevel' => CONTEXT_COURSECAT,
-        'archetypes' => array(
-            'manager' => CAP_ALLOW
-        )
-    ),
-
-    'tool/recyclebin:delete_protected_item' => array(
-        'captype' => 'write',
-        'riskbitmask' => RISK_DATALOSS,
         'contextlevel' => CONTEXT_COURSE,
         'archetypes' => array(
+            'teacher' => CAP_ALLOW,
+            'editingteacher' => CAP_ALLOW,
             'manager' => CAP_ALLOW
         )
     )
index 5c5efe5..27c32d8 100644 (file)
@@ -1,39 +1,42 @@
 <?xml version="1.0" encoding="UTF-8" ?>
-<XMLDB PATH="tool/recyclebin/db" VERSION="20150826" COMMENT="XMLDB file for Moodle tool/recyclebin"
+<XMLDB PATH="tool/recyclebin/db" VERSION="20160315" COMMENT="XMLDB file for Moodle tool/recyclebin"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:noNamespaceSchemaLocation="../../../lib/xmldb/xmldb.xsd"
 >
   <TABLES>
-    <TABLE NAME="tool_recyclebin_course" COMMENT="A list of recycled course modules">
+    <TABLE NAME="tool_recyclebin_course" COMMENT="A list of items in the course recycle bin">
       <FIELDS>
         <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
-        <FIELD NAME="course" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
+        <FIELD NAME="courseid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
         <FIELD NAME="section" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
         <FIELD NAME="module" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
         <FIELD NAME="name" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false"/>
-        <FIELD NAME="deleted" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
+        <FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
       </FIELDS>
       <KEYS>
         <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
+        <KEY NAME="courseid" TYPE="foreign" FIELDS="courseid" REFTABLE="course" REFFIELDS="id"/>
       </KEYS>
       <INDEXES>
-        <INDEX NAME="i_course" UNIQUE="false" FIELDS="course"/>
-        <INDEX NAME="i_deleted" UNIQUE="false" FIELDS="deleted"/>
+        <INDEX NAME="courseid" UNIQUE="false" FIELDS="courseid"/>
+        <INDEX NAME="timecreated" UNIQUE="false" FIELDS="timecreated"/>
       </INDEXES>
     </TABLE>
-    <TABLE NAME="tool_recyclebin_category" COMMENT="A list of deleted courses we have stored up">
+    <TABLE NAME="tool_recyclebin_category" COMMENT="A list of items in the category recycle bin">
       <FIELDS>
         <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
-        <FIELD NAME="category" TYPE="int" LENGTH="11" NOTNULL="true" SEQUENCE="false"/>
+        <FIELD NAME="categoryid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
         <FIELD NAME="shortname" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
-        <FIELD NAME="fullname" TYPE="char" LENGTH="254" NOTNULL="true" SEQUENCE="false"/>
-        <FIELD NAME="deleted" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
+        <FIELD NAME="fullname" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
+        <FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
       </FIELDS>
       <KEYS>
         <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
+        <KEY NAME="categoryid" TYPE="foreign" FIELDS="categoryid" REFTABLE="course_categories" REFFIELDS="id"/>
       </KEYS>
       <INDEXES>
-        <INDEX NAME="i_category" UNIQUE="false" FIELDS="category"/>
+        <INDEX NAME="categoryid" UNIQUE="false" FIELDS="categoryid"/>
+        <INDEX NAME="timecreated" UNIQUE="false" FIELDS="timecreated"/>
       </INDEXES>
     </TABLE>
   </TABLES>
index 90de95f..4036e27 100644 (file)
@@ -24,7 +24,7 @@
 
 $tasks = array(
     array(
-        'classname' => 'tool_recyclebin\task\cleanup_activities',
+        'classname' => 'tool_recyclebin\task\cleanup_course_bin',
         'blocking' => 0,
         'minute' => '*/30',
         'hour' => '*',
@@ -33,7 +33,7 @@ $tasks = array(
         'month' => '*'
     ),
     array(
-        'classname' => 'tool_recyclebin\task\cleanup_courses',
+        'classname' => 'tool_recyclebin\task\cleanup_category_bin',
         'blocking' => 0,
         'minute' => '*/30',
         'hour' => '*',
@@ -41,4 +41,4 @@ $tasks = array(
         'dayofweek' => '*',
         'month' => '*'
     )
-);
\ No newline at end of file
+);
index d27f68f..c509b27 100644 (file)
@@ -31,24 +31,42 @@ $action = optional_param('action', null, PARAM_ALPHA);
 $context = context::instance_by_id($contextid, MUST_EXIST);
 $PAGE->set_context($context);
 
-$description = '';
-
 // We could be a course or a category.
 switch ($context->contextlevel) {
-    case \CONTEXT_COURSE:
+    case CONTEXT_COURSE:
         require_login($context->instanceid);
-        require_capability('tool/recyclebin:view_item', $context);
 
-        $recyclebin = new \tool_recyclebin\course($context->instanceid);
-        $description = get_string('description_course', 'tool_recyclebin');
+        $recyclebin = new \tool_recyclebin\course_bin($context->instanceid);
+        if (!$recyclebin->can_view()) {
+            throw new required_capability_exception($context, 'tool/recyclebin:viewitems', 'nopermissions', '');
+        }
+
+        $PAGE->set_pagelayout('incourse');
+        // Set the $PAGE heading - this is also the same as the h2 heading.
+        $heading = format_string($COURSE->fullname, true, array('context' => $context)) . ': ' .
+            get_string('pluginname', 'tool_recyclebin');
+        $PAGE->set_heading($heading);
+
+        // Get the expiry to use later.
+        $expiry = get_config('tool_recyclebin', 'coursebinexpiry');
     break;
 
-    case \CONTEXT_COURSECAT:
+    case CONTEXT_COURSECAT:
         require_login();
-        require_capability('tool/recyclebin:view_course', $context);
 
-        $recyclebin = new \tool_recyclebin\category($context->instanceid);
-        $description = get_string('description_coursecat', 'tool_recyclebin');
+        $recyclebin = new \tool_recyclebin\category_bin($context->instanceid);
+        if (!$recyclebin->can_view()) {
+            throw new required_capability_exception($context, 'tool/recyclebin:viewitems', 'nopermissions', '');
+        }
+
+        $PAGE->set_pagelayout('admin');
+        // Set the $PAGE heading.
+        $PAGE->set_heading($COURSE->fullname);
+        // The h2 heading on the page is going to be different than the $PAGE heading.
+        $heading = $context->get_context_name() . ': ' . get_string('pluginname', 'tool_recyclebin');
+
+        // Get the expiry to use later.
+        $expiry = get_config('tool_recyclebin', 'categorybinexpiry');
     break;
 
     default:
@@ -79,7 +97,7 @@ if (!empty($action)) {
     switch ($action) {
         // Restore it.
         case 'restore':
-            if ($recyclebin->can_restore($item)) {
+            if ($recyclebin->can_restore()) {
                 $recyclebin->restore_item($item);
                 redirect($PAGE->url, get_string('alertrestored', 'tool_recyclebin', $item), 2);
             } else {
@@ -89,7 +107,7 @@ if (!empty($action)) {
 
         // Delete it.
         case 'delete':
-            if ($recyclebin->can_delete($item)) {
+            if ($recyclebin->can_delete()) {
                 $recyclebin->delete_item($item);
                 redirect($PAGE->url, get_string('alertdeleted', 'tool_recyclebin', $item), 2);
             } else {
@@ -112,32 +130,31 @@ $goback .= html_writer::end_tag('div');
 
 // Output header.
 echo $OUTPUT->header();
-echo $OUTPUT->heading($PAGE->title);
+echo $OUTPUT->heading($heading);
 
 // Grab our items, check there is actually something to display.
 $items = $recyclebin->get_items();
 
 // Nothing to show? Bail out early.
 if (empty($items)) {
-    echo $OUTPUT->box(get_string('emptybin', 'tool_recyclebin'));
+    echo $OUTPUT->box(get_string('noitemsinbin', 'tool_recyclebin'));
     echo $goback;
     echo $OUTPUT->footer();
     die;
 }
 
 // Start with a description.
-$expiry = get_config('tool_recyclebin', 'expiry');
 if ($expiry > 0) {
-    $description .= ' ' . get_string('descriptionexpiry', 'tool_recyclebin', $expiry);
+    $expirydisplay = format_time($expiry);
+    echo '<div class=\'alert\'>' . get_string('deleteexpirywarning', 'tool_recyclebin', $expirydisplay) . '</div>';
 }
-echo $OUTPUT->box($description, 'generalbox descriptionbox');
 
 // Define columns and headers.
-$firstcolstr = $context->contextlevel == \CONTEXT_COURSE ? 'activity' : 'course';
+$firstcolstr = $context->contextlevel == CONTEXT_COURSE ? 'activity' : 'course';
 $columns = array($firstcolstr, 'date', 'restore', 'delete');
 $headers = array(
     get_string($firstcolstr),
-    get_string('deleted', 'tool_recyclebin'),
+    get_string('datedeleted', 'tool_recyclebin'),
     get_string('restore'),
     get_string('delete')
 );
@@ -145,6 +162,8 @@ $headers = array(
 // Define a table.
 $table = new flexible_table('recyclebin');
 $table->define_columns($columns);
+$table->column_style('restore', 'text-align', 'center');
+$table->column_style('delete', 'text-align', 'center');
 $table->define_headers($headers);
 $table->define_baseurl($PAGE->url);
 $table->set_attribute('id', 'recycle-bin-table');
@@ -152,18 +171,19 @@ $table->setup();
 
 // Cache a list of modules.
 $modules = null;
-if ($context->contextlevel == \CONTEXT_COURSE) {
+if ($context->contextlevel == CONTEXT_COURSE) {
     $modules = $DB->get_records('modules');
 }
 
 // Add all the items to the table.
 $showempty = false;
+$canrestore = $recyclebin->can_restore();
 foreach ($items as $item) {
     $row = array();
 
     // Build item name.
     $name = $item->name;
-    if ($context->contextlevel == \CONTEXT_COURSE) {
+    if ($context->contextlevel == CONTEXT_COURSE) {
         if (isset($modules[$item->module])) {
             $mod = $modules[$item->module];
             $modname = get_string('modulename', $mod->name);
@@ -172,11 +192,11 @@ foreach ($items as $item) {
     }
 
     $row[] = $name;
-    $row[] = userdate($item->deleted);
+    $row[] = userdate($item->timecreated);
 
     // Build restore link.
-    if ($recyclebin->can_restore($item) && ($context->contextlevel == \CONTEXT_COURSECAT || isset($modules[$item->module]))) {
-        $restoreurl = new \moodle_url($PAGE->url, array(
+    if ($canrestore && ($context->contextlevel == CONTEXT_COURSECAT || isset($modules[$item->module]))) {
+        $restoreurl = new moodle_url($PAGE->url, array(
             'contextid' => $contextid,
             'itemid' => $item->id,
             'action' => 'restore',
@@ -191,17 +211,16 @@ foreach ($items as $item) {
     }
 
     // Build delete link.
-    if ($recyclebin->can_delete($item)) {
+    if ($recyclebin->can_delete()) {
         $showempty = true;
-        $delete = new \moodle_url($PAGE->url, array(
+        $delete = new moodle_url($PAGE->url, array(
             'contextid' => $contextid,
             'itemid' => $item->id,
             'action' => 'delete',
             'sesskey' => sesskey()
         ));
-        $delete = $OUTPUT->action_icon($delete, new pix_icon('t/delete',
-                get_string('delete'), '', array('class' => 'iconsmall')), null,
-                array('class' => 'action-icon recycle-bin-delete'));
+        $deleteaction = new confirm_action(get_string('deleteconfirm', 'tool_recyclebin'));
+        $delete = $OUTPUT->action_icon($delete, new pix_icon('t/delete', get_string('delete')), $deleteaction);
 
         $row[] = $delete;
     } else {
@@ -217,22 +236,19 @@ $table->finish_output();
 
 // Empty recyclebin link.
 if ($showempty) {
-    $empty = new \moodle_url($PAGE->url, array(
+    $emptylink = new moodle_url($PAGE->url, array(
         'contextid' => $contextid,
         'action' => 'empty',
         'sesskey' => sesskey()
     ));
-
-    echo $OUTPUT->single_button($empty, get_string('empty', 'tool_recyclebin'), 'post', array(
-        'class' => 'singlebutton recycle-bin-delete-all'
-    ));
+    $emptyaction = new confirm_action(get_string('deleteallconfirm', 'tool_recyclebin'));
+    echo $OUTPUT->action_link($emptylink, get_string('deleteall', 'tool_recyclebin'), $emptyaction);
 }
 
 echo $goback;
 
 // Confirmation JS.
-$PAGE->requires->strings_for_js(array('emptyconfirm', 'deleteconfirm'), 'tool_recyclebin');
-$PAGE->requires->js_init_call('M.tool_recyclebin.init');
+$PAGE->requires->strings_for_js(array('deleteallconfirm', 'deleteconfirm'), 'tool_recyclebin');
 
 // Output footer.
 echo $OUTPUT->footer();
index c860c0d..87c0185 100644 (file)
 
 defined('MOODLE_INTERNAL') || die();
 
-$string['pluginname'] = 'Recycle bin';
-
-$string['cleancourserecyclebin'] = 'Clean course recycle bins';
-$string['cleancategoryrecyclebin'] = 'Clean category recycle bins';
-
-$string['enablecourse'] = 'Enable course recycle bin';
-$string['enablecourse_desc'] = 'Enables or disables the course recycle bin';
-
-$string['expiry'] = 'Activity lifetime';
-$string['expiry_desc'] = 'How long should a deleted mod remain in the recycle bin?';
-
-$string['enablecategory'] = 'Enable category recycle bin';
-$string['enablecategory_desc'] = 'Enables or disables the category recycle bin';
-
-$string['course_expiry'] = 'Course lifetime';
-$string['course_expiry_desc'] = 'How long should a deleted course remain in the recycle bin?';
-
-$string['autohide'] = 'Auto hide?';
+$string['alertdeleted'] = '\'{$a->name}\' has been deleted.';
+$string['alertemptied'] = 'Recycle bin has been emptied.';
+$string['alertrestored'] = '\'{$a->name}\' has been restored.';
+$string['autohide'] = 'Auto hide';
 $string['autohide_desc'] = 'Automatically hides the recycle bin link when the bin is empty.';
-
-$string['protectedmods'] = 'Protected modules';
-$string['protectedmods_desc'] = 'Protected modules will not be automatically deleted.';
-
-$string['neverdelete'] = 'Never delete recycled items';
-$string['deleted'] = 'Date deleted';
-$string['empty'] = 'Delete all';
-
-$string['recyclebin:view_item'] = 'View course recycle bin items';
-$string['recyclebin:restore_item'] = 'Restore course recycle bin items';
-$string['recyclebin:delete_item'] = 'Delete course recycle bin items';
-$string['recyclebin:delete_protected_item'] = 'Delete protected course recycle bin items';
-
-$string['recyclebin:view_course'] = 'View category recycle bin items';
-$string['recyclebin:restore_course'] = 'Restore category recycle bin items';
-$string['recyclebin:delete_course'] = 'Delete category recycle bin items';
-
-$string['alertrestored'] = '{$a->name} has been restored';
-$string['alertdeleted'] = '{$a->name} has been deleted';
-$string['alertemptied'] = 'Recycle bin has been emptied';
-
-$string['event_stored_name'] = 'Item stored';
-$string['event_restored_name'] = 'Item restored';
-$string['event_purged_name'] = 'Item purged';
-
-$string['event_stored_description'] = 'Item stored with ID {$a->objectid}.';
-$string['event_restored_description'] = 'Item with ID {$a->objectid} restored.';
-$string['event_purged_description'] = 'Item with ID {$a->objectid} purged.';
-
-$string['description_course'] = 'Items that have been deleted from a course can be restored if they are still in the recycle bin, and will appear at the bottom of the section from which they were deleted.';
-$string['description_coursecat'] = 'Items that have been deleted from a category can be restored if they are still in the recycle bin.';
-$string['descriptionexpiry'] = 'Contents will be permanently deleted after {$a} days.';
-
-$string['emptybin'] = 'There are no items in the recycle bin.';
-$string['emptyconfirm'] = 'Are you sure you want to delete all items in the recycle bin?';
+$string['categorybinenable'] = 'Enable category recycle bin';
+$string['categorybinexpiry'] = 'Course lifetime';
+$string['categorybinexpiry_desc'] = 'How long should a deleted course remain in the recycle bin?';
+$string['coursebinenable'] = 'Enable course recycle bin';
+$string['coursebinexpiry'] = 'Item lifetime';
+$string['coursebinexpiry_desc'] = 'How long should a deleted item remain in the recycle bin?';
+$string['datedeleted'] = 'Date deleted';
+$string['deleteall'] = 'Delete all';
+$string['deleteallconfirm'] = 'Are you sure you want to delete all items from the recycle bin?';
 $string['deleteconfirm'] = 'Are you sure you want to delete the selected item from the recycle bin?';
-
+$string['deleteexpirywarning'] = 'Contents will be permanently deleted after {$a}.';
+$string['eventitemcreated'] = 'Item created';
+$string['eventitemcreated_desc'] = 'Item created with ID {$a->objectid}.';
+$string['eventitemdeleted'] = 'Item deleted';
+$string['eventitemdeleted_desc'] = 'Item with ID {$a->objectid} deleted.';
+$string['eventitemrestored'] = 'Item restored';
+$string['eventitemrestored_desc'] = 'Item with ID {$a->objectid} restored.';
 $string['invalidcontext'] = 'Invalid context supplied.';
-
+$string['neverdelete'] = 'Never delete recycled items';
+$string['noitemsinbin'] = 'There are no items in the recycle bin.';
 $string['notenabled'] = 'Sorry, but the recycle bin has been disabled by the administrator.';
+$string['pluginname'] = 'Recycle bin';
+$string['taskcleanupcategorybin'] = 'Cleanup category recycle bin';
+$string['taskcleanupcoursebin'] = 'Cleanup course recycle bin';
+$string['recyclebin:deleteitems'] = 'Delete recycle bin items';
+$string['recyclebin:restoreitems'] = 'Restore recycle bin items';
+$string['recyclebin:viewitems'] = 'View recycle bin items';
index da32e59..39d451b 100644 (file)
@@ -28,27 +28,28 @@ defined('MOODLE_INTERNAL') || die;
  * Adds a recycle bin link to the course admin menu.
  *
  * @param navigation_node $navigation The navigation node to extend
- * @param stdClass        $course     The course to object for the tool
- * @param context         $context    The context of the course
+ * @param stdClass $course The course to object for the tool
+ * @param context $context The context of the course
+ * @return void|null return null if we don't want to display the node.
  */
 function tool_recyclebin_extend_navigation_course($navigation, $course, $context) {
     global $PAGE;
 
-    $url = null;
-    $bin = null;
-    $settingnode = null;
-
     // Only add this settings item on non-site course pages.
-    if (!$PAGE->course || $PAGE->course->id == SITEID || !\tool_recyclebin\course::is_enabled()) {
+    if (!$PAGE->course || $PAGE->course->id == SITEID || !\tool_recyclebin\course_bin::is_enabled()) {
         return null;
     }
 
+    $coursebin = new \tool_recyclebin\course_bin($context->instanceid);
+
     // Check we can view the recycle bin.
-    if (!has_capability('tool/recyclebin:view_item', $context)) {
+    if (!$coursebin->can_view()) {
         return null;
     }
 
-    $bin = new \tool_recyclebin\course($context->instanceid);
+    $url = null;
+    $settingnode = null;
+
     $url = new moodle_url('/admin/tool/recyclebin/index.php', array(
         'contextid' => $context->id
     ));
@@ -56,7 +57,7 @@ function tool_recyclebin_extend_navigation_course($navigation, $course, $context
     // If we are set to auto-hide, check the number of items.
     $autohide = get_config('tool_recyclebin', 'autohide');
     if ($autohide) {
-        $items = $bin->get_items();
+        $items = $coursebin->get_items();
         if (empty($items)) {
             return null;
         }
@@ -71,7 +72,7 @@ function tool_recyclebin_extend_navigation_course($navigation, $course, $context
         navigation_node::NODETYPE_LEAF,
         'tool_recyclebin',
         'tool_recyclebin',
-        new pix_icon('e/cleanup_messy_code', $pluginname)
+        new pix_icon('trash', $pluginname, 'tool_recyclebin')
     );
 
     if ($PAGE->url->compare($url, URL_MATCH_BASE)) {
@@ -85,22 +86,28 @@ function tool_recyclebin_extend_navigation_course($navigation, $course, $context
  * Adds a recycle bin link to the course admin menu.
  *
  * @param navigation_node $navigation The navigation node to extend
- * @param context         $context    The context of the course
+ * @param context $context The context of the course
+ * @return void|null return null if we don't want to display the node.
  */
 function tool_recyclebin_extend_navigation_category_settings($navigation, $context) {
     global $PAGE;
 
-    $url = null;
-    $bin = null;
-    $settingnode = null;
+    // Check if it is enabled.
+    if (!\tool_recyclebin\category_bin::is_enabled()) {
+        return null;
+    }
+
+    $categorybin = new \tool_recyclebin\category_bin($context->instanceid);
 
     // Check we can view the recycle bin.
-    if (!has_capability('tool/recyclebin:view_course', $context) || !\tool_recyclebin\category::is_enabled()) {
+    if (!$categorybin->can_view()) {
         return null;
     }
 
+    $url = null;
+    $settingnode = null;
+
     // Add a link to the category recyclebin.
-    $bin = new \tool_recyclebin\category($context->instanceid);
     $url = new moodle_url('/admin/tool/recyclebin/index.php', array(
         'contextid' => $context->id
     ));
@@ -108,7 +115,7 @@ function tool_recyclebin_extend_navigation_category_settings($navigation, $conte
     // If we are set to auto-hide, check the number of items.
     $autohide = get_config('tool_recyclebin', 'autohide');
     if ($autohide) {
-        $items = $bin->get_items();
+        $items = $categorybin->get_items();
         if (empty($items)) {
             return null;
         }
@@ -123,7 +130,7 @@ function tool_recyclebin_extend_navigation_category_settings($navigation, $conte
         navigation_node::NODETYPE_LEAF,
         'tool_recyclebin',
         'tool_recyclebin',
-        new pix_icon('e/cleanup_messy_code', $pluginname)
+        new pix_icon('trash', $pluginname, 'tool_recyclebin')
     );
 
     if ($PAGE->url->compare($url, URL_MATCH_BASE)) {
@@ -139,9 +146,9 @@ function tool_recyclebin_extend_navigation_category_settings($navigation, $conte
  * @param \stdClass $cm The course module record.
  */
 function tool_recyclebin_pre_course_module_delete($cm) {
-    if (\tool_recyclebin\course::is_enabled()) {
-        $recyclebin = new \tool_recyclebin\course($cm->course);
-        $recyclebin->store_item($cm);
+    if (\tool_recyclebin\course_bin::is_enabled()) {
+        $coursebin = new \tool_recyclebin\course_bin($cm->course);
+        $coursebin->store_item($cm);
     }
 }
 
@@ -151,8 +158,25 @@ function tool_recyclebin_pre_course_module_delete($cm) {
  * @param \stdClass $course The course record.
  */
 function tool_recyclebin_pre_course_delete($course) {
-    if (\tool_recyclebin\category::is_enabled()) {
-        $recyclebin = new \tool_recyclebin\category($course->category);
-        $recyclebin->store_item($course);
+    // Delete all the items in the course recycle bin, regardless if it enabled or not.
+    // It may have been enabled, then disabled later on, so may still have content.
+    $coursebin = new \tool_recyclebin\course_bin($course->id);
+    $coursebin->delete_all_items();
+
+    if (\tool_recyclebin\category_bin::is_enabled()) {
+        $categorybin = new \tool_recyclebin\category_bin($course->category);
+        $categorybin->store_item($course);
     }
 }
+
+/**
+ * Hook called before we delete a category.
+ *
+ * @param \stdClass $category The category record.
+ */
+function tool_recyclebin_pre_course_category_delete($category) {
+    // Delete all the items in the category recycle bin, regardless if it enabled or not.
+    // It may have been enabled, then disabled later on, so may still have content.
+    $categorybin = new \tool_recyclebin\category_bin($category->id);
+    $categorybin->delete_all_items();
+}
diff --git a/admin/tool/recyclebin/module.js b/admin/tool/recyclebin/module.js
deleted file mode 100644 (file)
index 2df4842..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-// Added a confirmation dialogue when the empty recycle bin
-// button has been selected.
-M.tool_recyclebin = {
-    init: function (Y) {
-        // Confirmation dialogue function.
-        function confirmDialogue(e, str, callback) {
-            // Prevent the button from immediately performing its action.
-            e.preventDefault();
-
-            // Add a confirm dialogue box.
-            YUI().use('moodle-core-notification-confirm', function(Y) {
-                var confirm = new M.core.confirm({
-                    question: str,
-                    center: true,
-                    modal: true,
-                });
-
-                // Perform the button's action if "Yes" is selected.
-                confirm.on('complete-yes', callback, this);
-
-                // Render the confirm dialogue.
-                confirm.render().show();
-            });
-        }
-
-        // Perform this action when any "Delete" button/link is clicked.
-        Y.all('.recycle-bin-delete').each(function(node) {
-            node.on('click', function(e) {
-                // Get some strings from the Recycle bin lang file.
-                var str = M.util.get_string('deleteconfirm', 'tool_recyclebin');
-
-                // Get the URL that leads to emptying the recycle bin.
-                var urldelete = this.get('href');
-
-                // Show the dialogue.
-                confirmDialogue(e, str, function() {
-                    window.location = urldelete;
-                });
-            });
-        });
-
-        // Find the "Delete All" button and perform an action when it is clicked.
-        Y.one('.recycle-bin-delete-all input').on('click', function(e) {
-            // Get some strings from the Recycle bin lang file.
-            var str = M.util.get_string('emptyconfirm', 'tool_recyclebin');
-
-            // Show the dialogue.
-            confirmDialogue(e, str, function() {
-                Y.one('.recycle-bin-delete-all form').submit();
-            });
-        });
-    }
-};
diff --git a/admin/tool/recyclebin/pix/trash.png b/admin/tool/recyclebin/pix/trash.png
new file mode 100644 (file)
index 0000000..fc6aa0c
Binary files /dev/null and b/admin/tool/recyclebin/pix/trash.png differ
diff --git a/admin/tool/recyclebin/pix/trash.svg b/admin/tool/recyclebin/pix/trash.svg
new file mode 100644 (file)
index 0000000..a772952
--- /dev/null
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In  -->\r
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [\r
+       <!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">\r
+]>\r
+<svg version="1.1"\r
+        xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"\r
+        x="0px" y="0px" width="16px" height="16px" viewBox="0 0 16 16" style="overflow:visible;enable-background:new 0 0 16 16;"\r
+        xml:space="preserve" preserveAspectRatio="xMinYMid meet">\r
+<defs>\r
+</defs>\r
+<path style="fill:#999999;" d="M2,5v9c0,1.1,0.9,2,2,2h8c1.1,0,2-0.9,2-2V5H2z M5,13.5C5,13.8,4.8,14,4.5,14S4,13.8,4,13.5v-6\r
+       C4,7.2,4.2,7,4.5,7S5,7.2,5,7.5V13.5z M8.5,13.5C8.5,13.8,8.3,14,8,14s-0.5-0.2-0.5-0.5v-6C7.5,7.2,7.7,7,8,7s0.5,0.2,0.5,0.5V13.5z\r
+        M12,13.5c0,0.3-0.2,0.5-0.5,0.5S11,13.8,11,13.5v-6C11,7.2,11.2,7,11.5,7S12,7.2,12,7.5V13.5z M0,4c0-1.1,0.9-2,2-2h4v0\r
+       c0-1.1,0.9-2,2-2s2,0.9,2,2v0h4c1.1,0,2,0.9,2,2H0z"/>\r
+</svg>\r
index 6ae49dc..da19ea7 100644 (file)
@@ -30,68 +30,38 @@ if ($hassiteconfig) {
     $settings = new admin_settingpage('tool_recyclebin', get_string('pluginname', 'tool_recyclebin'));
     $ADMIN->add('tools', $settings);
 
-    $lifetimes = array(
-        0    => new lang_string('neverdelete', 'tool_recyclebin'),
-        1000 => new lang_string('numdays', '', 1000),
-        365  => new lang_string('numdays', '', 365),
-        180  => new lang_string('numdays', '', 180),
-        150  => new lang_string('numdays', '', 150),
-        120  => new lang_string('numdays', '', 120),
-        90   => new lang_string('numdays', '', 90),
-        60   => new lang_string('numdays', '', 60),
-        35   => new lang_string('numdays', '', 35),
-        21   => new lang_string('numdays', '', 21),
-        14   => new lang_string('numdays', '', 14),
-        10   => new lang_string('numdays', '', 10),
-        5    => new lang_string('numdays', '', 5),
-        2    => new lang_string('numdays', '', 2)
-    );
-
     $settings->add(new admin_setting_configcheckbox(
-        'tool_recyclebin/enablecourse',
-        new lang_string('enablecourse', 'tool_recyclebin'),
-        new lang_string('enablecourse_desc', 'tool_recyclebin'),
+        'tool_recyclebin/coursebinenable',
+        new lang_string('coursebinenable', 'tool_recyclebin'),
+        '',
         1
     ));
 
-    $settings->add(new admin_setting_configselect(
-        'tool_recyclebin/expiry',
-        new lang_string('expiry', 'tool_recyclebin'),
-        new lang_string('expiry_desc', 'tool_recyclebin'),
-        0,
-        $lifetimes
+    $settings->add(new admin_setting_configduration(
+        'tool_recyclebin/coursebinexpiry',
+        new lang_string('coursebinexpiry', 'tool_recyclebin'),
+        new lang_string('coursebinexpiry_desc', 'tool_recyclebin'),
+        WEEKSECS
     ));
 
-
     $settings->add(new admin_setting_configcheckbox(
-        'tool_recyclebin/enablecategory',
-        new lang_string('enablecategory', 'tool_recyclebin'),
-        new lang_string('enablecategory_desc', 'tool_recyclebin'),
+        'tool_recyclebin/categorybinenable',
+        new lang_string('categorybinenable', 'tool_recyclebin'),
+        '',
         1
     ));
 
-    $settings->add(new admin_setting_configselect(
-        'tool_recyclebin/course_expiry',
-        new lang_string('course_expiry', 'tool_recyclebin'),
-        new lang_string('course_expiry_desc', 'tool_recyclebin'),
-        0,
-        $lifetimes
+    $settings->add(new admin_setting_configduration(
+        'tool_recyclebin/categorybinexpiry',
+        new lang_string('categorybinexpiry', 'tool_recyclebin'),
+        new lang_string('categorybinexpiry_desc', 'tool_recyclebin'),
+        WEEKSECS
     ));
 
-    unset($lifetimes);
-
     $settings->add(new admin_setting_configcheckbox(
         'tool_recyclebin/autohide',
         new lang_string('autohide', 'tool_recyclebin'),
         new lang_string('autohide_desc', 'tool_recyclebin'),
-        0
-    ));
-
-    $settings->add(new admin_setting_configmultiselect(
-        'tool_recyclebin/protectedmods',
-        new lang_string('protectedmods', 'tool_recyclebin'),
-        new lang_string('protectedmods_desc', 'tool_recyclebin'),
-        array(),
-        $DB->get_records_menu('modules', array('visible' => 1), 'name ASC', 'id, name')
+        1
     ));
 }
diff --git a/admin/tool/recyclebin/styles.css b/admin/tool/recyclebin/styles.css
deleted file mode 100644 (file)
index 28ca97a..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-/* Make the icons center. */
-table#recycle-bin-table .c2,
-table#recycle-bin-table .c3 {
-    text-align: center;
-}
\ No newline at end of file
index f68c08a..7c20c3e 100644 (file)
@@ -16,9 +16,12 @@ Feature: Backup user data
       | user | course | role |
       | teacher1 | C1 | editingteacher |
       | student1 | C1 | student |
+    And the following config values are set as admin:
+      | coursebinenable | 1 | tool_recyclebin |
+      | autohide | 0 | tool_recyclebin |
 
   @javascript
-  Scenario: Delete and restore a quiz.
+  Scenario: Delete and restore a quiz with user data
     Given I log in as "teacher1"
     And I follow "Course 1"
     And I turn editing mode on
@@ -33,8 +36,8 @@ Feature: Backup user data
       | Feedback for the response 'True'.  | So you think it is true                 |
       | Feedback for the response 'False'. | So you think it is false                |
     And I add a "True/False" question to the "Quiz 1" quiz with:
-      | Question name                      | TF2                          |
-      | Question text                      | Second question               |
+      | Question name                      | TF2                                     |
+      | Question text                      | Second question                         |
       | General feedback                   | Thank you, this is the general feedback |
       | Correct answer                     | False                                   |
       | Feedback for the response 'True'.  | So you think it is true                 |
@@ -46,20 +49,21 @@ Feature: Backup user data
     And I press "Attempt quiz now"
     And I click on "True" "radio" in the "First question" "question"
     And I click on "False" "radio" in the "Second question" "question"
-    And I press "Next"
+    And I press "Finish attempt"
     And I press "Submit all and finish"
     And I click on "Submit all and finish" "button" in the "Confirmation" "dialogue"
-    Then I should see "5.00 out of 10.00"
-    Given I log out
+    And I should see "5.00 out of 10.00"
+    And I log out
     And I log in as "teacher1"
     And I follow "Course 1"
     And I turn editing mode on
     And I delete "Quiz 1" activity
     And I follow "Recycle bin"
+    And I should see "Quiz 1"
     And I follow "Restore"
     And I log out
     And I log in as "student1"
     And I follow "Course 1"
-    And I follow "Grades"
+    When I navigate to "Grades" node in "Course administration"
     Then "Quiz 1" row "Grade" column of "user-grade" table should contain "5"
     And "Quiz 1" row "Percentage" column of "user-grade" table should contain "50"
index e8e0fb2..85e5d57 100644 (file)
@@ -1,8 +1,8 @@
 @tool @tool_recyclebin
 Feature: Basic recycle bin functionality
   As a teacher
-  I want be able to recover deleted content
-  So that I can fix a mistake or accidently deletion
+  I want be able to recover deleted content and manage the recycle bin content
+  So that I can fix an accidental deletion and clean the recycle bin
 
   Background: Course with teacher exists.
     Given the following "users" exist:
@@ -12,33 +12,95 @@ Feature: Basic recycle bin functionality
     And the following "courses" exist:
       | fullname | shortname |
       | Course 1 | C1 |
+      | Course 2 | C2 |
     And the following "course enrolments" exist:
       | user | course | role |
       | teacher1 | C1 | editingteacher |
+    And the following config values are set as admin:
+      | coursebinenable | 1 | tool_recyclebin |
+      | categorybinenable | 1 | tool_recyclebin |
+      | coursebinexpiry | 604800 | tool_recyclebin |
+      | categorybinexpiry | 1209600 | tool_recyclebin |
+      | autohide | 0 | tool_recyclebin |
 
-  Scenario: Restore and delete an assingnment.
+  Scenario: Restore a deleted assignment
     Given I log in as "teacher1"
     And I follow "Course 1"
     And I turn editing mode on
-    And I add a "Page" to section "1" and I fill the form with:
-      | Name                | Test page |
-      | Description         | Test   |
-      | Page content        | Test   |
-    When I delete "Test page" activity
-    And I follow "Recycle bin"
-    Then I should see "Test page"
-    When I follow "Restore"
-    Then I should see "Test page has been restored"
-    When I wait to be redirected
+    And I add a "Assignment" to section "1" and I fill the form with:
+      | Assignment name | Test assign |
+      | Description | Test |
+    And I delete "Test assign" activity
+    When I follow "Recycle bin"
+    Then I should see "Test assign"
+    And I should see "Contents will be permanently deleted after 7 days"
+    And I follow "Restore"
+    And I should see "'Test assign' has been restored"
+    And I wait to be redirected
     And I am on homepage
     And I follow "Course 1"
-    Then I should see "Test page" in the "Topic 1" "section"
-    When I delete "Test page" activity
+    And I should see "Test assign" in the "Topic 1" "section"
+
+  Scenario: Restore a deleted course
+    Given I log in as "admin"
+    And I go to the courses management page
+    And I click on "delete" action for "Course 2" in management course listing
+    And I press "Delete"
+    And I should see "Deleting C2"
+    And I should see "C2 has been completely deleted"
+    And I press "Continue"
+    And I go to the courses management page
+    And I should not see "Course 2" in the "#course-listing" "css_element"
+    When I follow "Recycle bin"
+    Then I should see "Course 2"
+    And I should see "Contents will be permanently deleted after 14 days"
+    And I follow "Restore"
+    And I should see "'Course 2' has been restored"
+    And I wait to be redirected
+    And I go to the courses management page
+    And I should see "Course 2" in the "#course-listing" "css_element"
+
+  @javascript
+  Scenario: Deleting a single item from the recycle bin
+    Given I log in as "teacher1"
+    And I follow "Course 1"
+    And I turn editing mode on
+    And I add a "Assignment" to section "1" and I fill the form with:
+      | Assignment name | Test assign |
+      | Description | Test |
+    And I delete "Test assign" activity
     And I follow "Recycle bin"
-    Then I should see "Test page"
-    When I follow "Delete"
-    Then I should see "Test page has been deleted"
-    When I wait to be redirected
-    And I am on homepage
+    When I click on "Delete" "link"
+    Then I should see "Are you sure you want to delete the selected item from the recycle bin?"
+    And I press "Cancel"
+    And I should see "Test assign"
+    And I click on "Delete" "link"
+    And I press "Yes"
+    And I should see "'Test assign' has been deleted"
+    And I should see "There are no items in the recycle bin."
+
+  @javascript
+  Scenario: Deleting all the items from the recycle bin
+    Given I log in as "teacher1"
     And I follow "Course 1"
-    Then I should not see "Test page" in the "Topic 1" "section"
+    And I turn editing mode on
+    And I add a "Assignment" to section "1" and I fill the form with:
+      | Assignment name | Test assign 1 |
+      | Description | Test 1 |
+    And I add a "Assignment" to section "1" and I fill the form with:
+      | Assignment name | Test assign 2 |
+      | Description | Test 2 |
+    And I delete "Test assign 1" activity
+    And I delete "Test assign 2" activity
+    And I follow "Recycle bin"
+    And I should see "Test assign 1"
+    And I should see "Test assign 2"
+    When I click on "Delete all" "link"
+    Then I should see "Are you sure you want to delete all items from the recycle bin?"
+    And I press "Cancel"
+    And I should see "Test assign 1"
+    And I should see "Test assign 2"
+    And I click on "Delete all" "link"
+    And I press "Yes"
+    And I should see "Recycle bin has been emptied"
+    And I should see "There are no items in the recycle bin."
diff --git a/admin/tool/recyclebin/tests/behat/delete_confirmation.feature b/admin/tool/recyclebin/tests/behat/delete_confirmation.feature
deleted file mode 100644 (file)
index 5d0e361..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-@tool @tool_recyclebin
-Feature: Delete confirmation
-    As a teacher
-    I want to be prompted before I permanently delete something
-    So that I do not make a mistake again
-
-Background:
-    Given the following "users" exist:
-        | username | firstname | lastname | email |
-        | teacher1 | Teacher | 1 | teacher@asd.com |
-    Given the following "courses" exist:
-        | fullname | shortname |
-        | Course 1 | C1 |
-    And the following "course enrolments" exist:
-        | user | course | role |
-        | teacher1 | C1 | editingteacher |
-    And I log in as "teacher1"
-    And I follow "Course 1"
-    And I turn editing mode on
-    And I add a "Page" to section "1" and I fill the form with:
-      | Name                | Test page |
-      | Description         | Test   |
-      | Page content        | Test   |
-    And I delete "Test page" activity
-    And I follow "Recycle bin"
-
-@javascript
-Scenario: Confirm single delete
-    When I click on "Delete" "link"
-    Then I should see "Are you sure you want to delete the selected item in the recycle bin?"
-    And I press "No"
-    And I should see "Test page"
-    When I click on "Delete" "link"
-    And I press "Yes"
-    And I wait to be redirected
-    Then I should see "There are no items in the recycle bin."
-
-@javascript
-Scenario: Confirm empty bin
-    When I press "Empty recycle bin"
-    Then I should see "Are you sure you want to delete all items in the recycle bin?"
-    And I press "No"
-    And I should see "Test page"
-    When I press "Empty recycle bin"
-    And I press "Yes"
-    And I wait to be redirected
-    Then I should see "There are no items in the recycle bin."
diff --git a/admin/tool/recyclebin/tests/behat/description.feature b/admin/tool/recyclebin/tests/behat/description.feature
deleted file mode 100644 (file)
index f2e813d..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-@tool @tool_recyclebin
-Feature: Description of recycle bin and expiry
-    As a teacher
-    I want to know what the recycle bin will do and how long contents last in the bin
-    So that I can better understand the tool
-
-Scenario: Description should show when the recycle bin will clean up files.
-    Given the following "users" exist:
-        | username | firstname | lastname | email |
-        | teacher1 | Teacher | 1 | teacher@asd.com |
-    Given the following "courses" exist:
-        | fullname | shortname |
-        | Course 1 | C1 |
-    And the following "course enrolments" exist:
-        | user | course | role |
-        | teacher1 | C1 | editingteacher |
-    And I log in as "teacher1"
-    And I follow "Course 1"
-    And I turn editing mode on
-    And I add a "Page" to section "1" and I fill the form with:
-      | Name                | Test page |
-      | Description         | Test   |
-      | Page content        | Test   |
-    When I delete "Test page" activity
-    And I follow "Recycle bin"
-    # Default expiry is 0 (never).
-    Then I should not see "Contents will be permanently deleted"
-    # Test changing expiry to something else.
-    When the following config values are set as admin:
-        | expiry | 10 | tool_recyclebin |
-    # Step "I reload the page" doesn't work outside of javascript.
-    And I follow "Recycle bin"
-    Then I should see "Contents will be permanently deleted after 10 days"
\ No newline at end of file
diff --git a/admin/tool/recyclebin/tests/behat/logs_test.feature b/admin/tool/recyclebin/tests/behat/logs_test.feature
deleted file mode 100644 (file)
index 1b6506f..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-@tool @tool_recyclebin
-Feature: Recycle bin refinements
-  As a teacher
-  I want the log to reflect the recycle bin's actions
-
-  Background: Course with teacher exists.
-    Given the following "users" exist:
-      | username | firstname | lastname | email |
-      | teacher1 | Teacher | 1 | teacher@asd.com |
-    And the following "courses" exist:
-      | fullname | shortname |
-      | Course 1 | C1 |
-    And the following "course enrolments" exist:
-      | user | course | role |
-      | teacher1 | C1 | editingteacher |
-
-  Scenario: Delete an assignment and check if it got logged.
-    Given I log in as "teacher1"
-    And I follow "Course 1"
-    And I turn editing mode on
-    And I add a "Page" to section "1" and I fill the form with:
-      | Name                | Test page |
-      | Description         | Test   |
-      | Page content        | Test   |
-    And I delete "Test page" activity
-    And I follow "Recycle bin"
-    When I click on "Delete" "link"
-    # Uncomment if running with javascript.
-    #And I press "Yes"
-    And I wait to be redirected
-    And I follow "C1"
-    And I expand "Reports" node
-    And I follow "Logs"
-    And I click on "Get these logs" "link_or_button"
-    Then I should see "Item stored"
-    And I should see "Item purged"
diff --git a/admin/tool/recyclebin/tests/category_bin_test.php b/admin/tool/recyclebin/tests/category_bin_test.php
new file mode 100644 (file)
index 0000000..5e3f85d
--- /dev/null
@@ -0,0 +1,168 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Recycle bin tests.
+ *
+ * @package    tool_recyclebin
+ * @copyright  2015 University of Kent
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Recycle bin category tests.
+ *
+ * @package    tool_recyclebin
+ * @copyright  2015 University of Kent
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class tool_recyclebin_category_bin_tests extends advanced_testcase {
+
+    /**
+     * @var stdClass $course
+     */
+    protected $course;
+
+    /**
+     * Setup for each test.
+     */
+    protected function setUp() {
+        $this->resetAfterTest();
+        $this->setAdminUser();
+
+        // We want the category bin to be enabled.
+        set_config('categorybinenable', 1, 'tool_recyclebin');
+
+        $this->course = $this->getDataGenerator()->create_course();
+    }
+
+    /**
+     * Check that our hook is called when a course is deleted.
+     */
+    public function test_pre_course_delete_hook() {
+        global $DB;
+
+        // Should have nothing in the recycle bin.
+        $this->assertEquals(0, $DB->count_records('tool_recyclebin_category'));
+
+        delete_course($this->course, false);
+
+        // Check the course is now in the recycle bin.
+        $this->assertEquals(1, $DB->count_records('tool_recyclebin_category'));
+
+        // Try with the API.
+        $recyclebin = new \tool_recyclebin\category_bin($this->course->category);
+        $this->assertEquals(1, count($recyclebin->get_items()));
+    }
+
+    /**
+     * Check that our hook is called when a course is deleted.
+     */
+    public function test_pre_course_category_delete_hook() {
+        global $DB;
+
+        // Should have nothing in the recycle bin.
+        $this->assertEquals(0, $DB->count_records('tool_recyclebin_category'));
+
+        delete_course($this->course, false);
+
+        // Check the course is now in the recycle bin.
+        $this->assertEquals(1, $DB->count_records('tool_recyclebin_category'));
+
+        // Now let's delete the course category.
+        $category = coursecat::get($this->course->category);
+        $category->delete_full(false);
+
+        // Check that the course was deleted from the category recycle bin.
+        $this->assertEquals(0, $DB->count_records('tool_recyclebin_category'));
+    }
+
+    /**
+     * Test that we can restore recycle bin items.
+     */
+    public function test_restore() {
+        global $DB;
+
+        delete_course($this->course, false);
+
+        $recyclebin = new \tool_recyclebin\category_bin($this->course->category);
+        foreach ($recyclebin->get_items() as $item) {
+            $recyclebin->restore_item($item);
+        }
+
+        // Check that it was restored and removed from the recycle bin.
+        $this->assertEquals(2, $DB->count_records('course')); // Site course and the course we restored.
+        $this->assertEquals(0, count($recyclebin->get_items()));
+    }
+
+    /**
+     * Test that we can delete recycle bin items.
+     */
+    public function test_delete() {
+        global $DB;
+
+        delete_course($this->course, false);
+
+        $recyclebin = new \tool_recyclebin\category_bin($this->course->category);
+        foreach ($recyclebin->get_items() as $item) {
+            $recyclebin->delete_item($item);
+        }
+
+        // Item was deleted, so no course was restored.
+        $this->assertEquals(1, $DB->count_records('course')); // Just the site course.
+        $this->assertEquals(0, count($recyclebin->get_items()));
+    }
+
+    /**
+     * Test the cleanup task.
+     */
+    public function test_cleanup_task() {
+        global $DB;
+
+        // Set the expiry to 1 week.
+        set_config('categorybinexpiry', WEEKSECS, 'tool_recyclebin');
+
+        delete_course($this->course, false);
+
+        $recyclebin = new \tool_recyclebin\category_bin($this->course->category);
+
+        // Set deleted date to the distant past.
+        foreach ($recyclebin->get_items() as $item) {
+            $item->timecreated = time() - WEEKSECS;
+            $DB->update_record('tool_recyclebin_category', $item);
+        }
+
+        // Create another course to delete.
+        $course = $this->getDataGenerator()->create_course();
+        delete_course($course, false);
+
+        // Should now be two courses in the recycle bin.
+        $this->assertEquals(2, count($recyclebin->get_items()));
+
+        // Execute cleanup task.
+        $this->expectOutputRegex("/\[tool_recyclebin\] Deleting item '\d+' from the category recycle bin/");
+        $task = new \tool_recyclebin\task\cleanup_category_bin();
+        $task->execute();
+
+        // Task should only have deleted the course where we updated the time.
+        $courses = $recyclebin->get_items();
+        $this->assertEquals(1, count($courses));
+        $course = reset($courses);
+        $this->assertEquals('Test course 2', $course->fullname);
+    }
+}
diff --git a/admin/tool/recyclebin/tests/category_test.php b/admin/tool/recyclebin/tests/category_test.php
deleted file mode 100644 (file)
index 5f20e71..0000000
+++ /dev/null
@@ -1,124 +0,0 @@
-<?php
-// This file is part of Moodle - http://moodle.org/
-//
-// Moodle is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Moodle is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
-
-/**
- * Recycle bin tests.
- *
- * @package    tool_recyclebin
- * @copyright  2015 University of Kent
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-defined('MOODLE_INTERNAL') || die();
-
-/**
- * Recycle bin category tests.
- *
- * @package    tool_recyclebin
- * @copyright  2015 University of Kent
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class tool_recyclebin_category_tests extends \advanced_testcase
-{
-    /**
-     * Setup for each test.
-     */
-    protected function setUp() {
-        global $DB;
-
-        $this->resetAfterTest(true);
-        $this->setAdminUser();
-
-        $this->before = $DB->count_records('course');
-        $this->course = $this->getDataGenerator()->create_course();
-    }
-
-    /**
-     * Check that our hook is called when a course is deleted.
-     */
-    public function test_hook() {
-        global $DB;
-
-        $this->assertEquals($this->before + 1, $DB->count_records('course'));
-        delete_course($this->course, false);
-        $this->assertEquals($this->before, $DB->count_records('course'));
-
-        // Try with the API.
-        $recyclebin = new \tool_recyclebin\category($this->course->category);
-        $this->assertEquals(1, count($recyclebin->get_items()));
-    }
-
-    /**
-     * Run a bunch of tests to make sure we can restore courses.
-     */
-    public function test_restore() {
-        global $DB;
-
-        delete_course($this->course, false);
-        $this->assertEquals($this->before, $DB->count_records('course'));
-
-        $recyclebin = new \tool_recyclebin\category($this->course->category);
-        foreach ($recyclebin->get_items() as $item) {
-            $recyclebin->restore_item($item);
-        }
-
-        $this->assertEquals($this->before + 1, $DB->count_records('course'));
-        $this->assertEquals(0, count($recyclebin->get_items()));
-    }
-
-    /**
-     * Run a bunch of tests to make sure we can purge courses.
-     */
-    public function test_purge() {
-        global $DB;
-
-        delete_course($this->course, false);
-        $this->assertEquals($this->before, $DB->count_records('course'));
-
-        $recyclebin = new \tool_recyclebin\category($this->course->category);
-        foreach ($recyclebin->get_items() as $item) {
-            $recyclebin->delete_item($item);
-        }
-
-        $this->assertEquals($this->before, $DB->count_records('course'));
-        $this->assertEquals(0, count($recyclebin->get_items()));
-    }
-
-    /**
-     * Test the cleanup/purge task.
-     */
-    public function test_purge_task() {
-        global $DB;
-
-        set_config('course_expiry', 1, 'tool_recyclebin');
-
-        delete_course($this->course, false);
-        $this->assertEquals($this->before, $DB->count_records('course'));
-
-        // Set deleted date to the distant past.
-        $recyclebin = new \tool_recyclebin\category($this->course->category);
-        foreach ($recyclebin->get_items() as $item) {
-            $item->deleted = 1;
-            $DB->update_record('tool_recyclebin_category', $item);
-        }
-        // Execute cleanup task.
-        $task = new tool_recyclebin\task\cleanup_courses();
-        $task->execute();
-
-        $this->assertEquals($this->before, $DB->count_records('course'));
-        $this->assertEquals(0, count($recyclebin->get_items()));
-    }
-}
diff --git a/admin/tool/recyclebin/tests/course_bin_test.php b/admin/tool/recyclebin/tests/course_bin_test.php
new file mode 100644 (file)
index 0000000..edfa1a2
--- /dev/null
@@ -0,0 +1,160 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Recycle bin tests.
+ *
+ * @package    tool_recyclebin
+ * @copyright  2015 University of Kent
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Recycle bin course tests.
+ *
+ * @package    tool_recyclebin
+ * @copyright  2015 University of Kent
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class tool_recyclebin_course_bin_tests extends advanced_testcase {
+
+    /**
+     * @var stdClass $course
+     */
+    protected $course;
+
+    /**
+     * @var stdClass the quiz record
+     */
+    protected $quiz;
+
+    /**
+     * Setup for each test.
+     */
+    protected function setUp() {
+        $this->resetAfterTest(true);
+        $this->setAdminUser();
+
+        // We want the course bin to be enabled.
+        set_config('coursebinenable', 1, 'tool_recyclebin');
+
+        $this->course = $this->getDataGenerator()->create_course();
+        $this->quiz = $this->getDataGenerator()->get_plugin_generator('mod_quiz')->create_instance(array(
+            'course' => $this->course->id
+        ));
+    }
+
+    /**
+     * Check that our hook is called when an activity is deleted.
+     */
+    public function test_pre_course_module_delete_hook() {
+        global $DB;
+
+        // Should have nothing in the recycle bin.
+        $this->assertEquals(0, $DB->count_records('tool_recyclebin_course'));
+
+        // Delete the course module.
+        course_delete_module($this->quiz->cmid);
+
+        // Check the course module is now in the recycle bin.
+        $this->assertEquals(1, $DB->count_records('tool_recyclebin_course'));
+
+        // Try with the API.
+        $recyclebin = new \tool_recyclebin\course_bin($this->course->id);
+        $this->assertEquals(1, count($recyclebin->get_items()));
+    }
+
+    /**
+     * Test that we can restore recycle bin items.
+     */
+    public function test_restore() {
+        global $DB;
+
+        // Delete the course module.
+        course_delete_module($this->quiz->cmid);
+
+        // Try restoring.
+        $recyclebin = new \tool_recyclebin\course_bin($this->course->id);
+        foreach ($recyclebin->get_items() as $item) {
+            $recyclebin->restore_item($item);
+        }
+
+        // Check that it was restored and removed from the recycle bin.
+        $this->assertEquals(1, $DB->count_records('course_modules'));
+        $this->assertEquals(0, count($recyclebin->get_items()));
+    }
+
+    /**
+     * Test that we can delete recycle bin items.
+     */
+    public function test_delete() {
+        global $DB;
+
+        // Delete the course module.
+        course_delete_module($this->quiz->cmid);
+
+        // Try purging.
+        $recyclebin = new \tool_recyclebin\course_bin($this->course->id);
+        foreach ($recyclebin->get_items() as $item) {
+            $recyclebin->delete_item($item);
+        }
+
+        // Item was deleted, so no course module was restored.
+        $this->assertEquals(0, $DB->count_records('course_modules'));
+        $this->assertEquals(0, count($recyclebin->get_items()));
+    }
+
+    /**
+     * Test the cleanup task.
+     */
+    public function test_cleanup_task() {
+        global $DB;
+
+        set_config('coursebinexpiry', WEEKSECS, 'tool_recyclebin');
+
+        // Delete the quiz.
+        course_delete_module($this->quiz->cmid);
+
+        // Set deleted date to the distant past.
+        $recyclebin = new \tool_recyclebin\course_bin($this->course->id);
+        foreach ($recyclebin->get_items() as $item) {
+            $item->timecreated = time() - WEEKSECS;
+            $DB->update_record('tool_recyclebin_course', $item);
+        }
+
+        // Create another module we are going to delete, but not alter the time it was placed in the recycle bin.
+        $book = $this->getDataGenerator()->get_plugin_generator('mod_book')->create_instance(array(
+            'course' => $this->course->id));
+
+        course_delete_module($book->cmid);
+
+        // Should have 2 items now.
+        $this->assertEquals(2, count($recyclebin->get_items()));
+
+        // Execute cleanup task.
+        $this->expectOutputRegex("/\[tool_recyclebin\] Deleting item '\d+' from the course recycle bin/");
+        $task = new \tool_recyclebin\task\cleanup_course_bin();
+        $task->execute();
+
+        // Should only have the book as it was not due to be deleted.
+        $items = $recyclebin->get_items();
+        $this->assertEquals(1, count($items));
+        $deletedbook = reset($items);
+        $this->assertEquals($book->name, $deletedbook->name);
+    }
+}
diff --git a/admin/tool/recyclebin/tests/course_test.php b/admin/tool/recyclebin/tests/course_test.php
deleted file mode 100644 (file)
index 8e9f49f..0000000
+++ /dev/null
@@ -1,138 +0,0 @@
-<?php
-// This file is part of Moodle - http://moodle.org/
-//
-// Moodle is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Moodle is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
-
-/**
- * Recycle bin tests.
- *
- * @package    tool_recyclebin
- * @copyright  2015 University of Kent
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-defined('MOODLE_INTERNAL') || die();
-
-/**
- * Recycle bin course tests.
- *
- * @package    tool_recyclebin
- * @copyright  2015 University of Kent
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class tool_recyclebin_course_tests extends \advanced_testcase
-{
-    /**
-     * Setup for each test.
-     */
-    protected function setUp() {
-        global $DB;
-
-        $this->resetAfterTest(true);
-        $this->setAdminUser();
-
-        $this->course = $this->getDataGenerator()->create_course();
-        $this->before = $DB->count_records('course_modules');
-        $generator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
-        $this->instance = $generator->create_instance(array(
-            'course' => $this->course->id
-        ));
-    }
-
-    /**
-     * Check that our hook is called when an activity is deleted.
-     */
-    public function test_hook() {
-        global $DB;
-
-        $this->assertEquals($this->before + 1, $DB->count_records('course_modules'));
-        $this->assertEquals(0, $DB->count_records('tool_recyclebin_course'));
-
-        // Delete the CM.
-        course_delete_module($this->instance->cmid);
-
-        $this->assertEquals($this->before, $DB->count_records('course_modules'));
-        $this->assertEquals(1, $DB->count_records('tool_recyclebin_course'));
-
-        // Try with the API.
-        $recyclebin = new \tool_recyclebin\course($this->course->id);
-        $this->assertEquals(1, count($recyclebin->get_items()));
-    }
-
-    /**
-     * Run a bunch of tests to make sure we can restore mods.
-     */
-    public function test_restore() {
-        global $DB;
-
-        // Delete the CM.
-        course_delete_module($this->instance->cmid);
-
-        // Try restoring.
-        $recyclebin = new \tool_recyclebin\course($this->course->id);
-        foreach ($recyclebin->get_items() as $item) {
-            $recyclebin->restore_item($item);
-        }
-
-        $this->assertEquals($this->before + 1, $DB->count_records('course_modules'));
-        $this->assertEquals(0, $DB->count_records('tool_recyclebin_course'));
-        $this->assertEquals(0, count($recyclebin->get_items()));
-    }
-
-    /**
-     * Run a bunch of tests to make sure we can purge mods.
-     */
-    public function test_purge() {
-        global $DB;
-
-        // Delete the CM.
-        course_delete_module($this->instance->cmid);
-
-        // Try purging.
-        $recyclebin = new \tool_recyclebin\course($this->course->id);
-        foreach ($recyclebin->get_items() as $item) {
-            $recyclebin->delete_item($item);
-        }
-
-        $this->assertEquals($this->before, $DB->count_records('course_modules'));
-        $this->assertEquals(0, $DB->count_records('tool_recyclebin_course'));
-        $this->assertEquals(0, count($recyclebin->get_items()));
-    }
-
-    /**
-     * Test the cleanup/purge task.
-     */
-    public function test_purge_task() {
-        global $DB;
-
-        set_config('expiry', 1, 'tool_recyclebin');
-
-        // Delete the CM.
-        course_delete_module($this->instance->cmid);
-
-        // Set deleted date to the distant past.
-        $recyclebin = new \tool_recyclebin\course($this->course->id);
-        foreach ($recyclebin->get_items() as $item) {
-            $item->deleted = 1;
-            $DB->update_record('tool_recyclebin_course', $item);
-        }
-        // Execute cleanup task.
-        $task = new tool_recyclebin\task\cleanup_activities();
-        $task->execute();
-
-        $this->assertEquals($this->before, $DB->count_records('course_modules'));
-        $this->assertEquals(0, $DB->count_records('tool_recyclebin_course'));
-        $this->assertEquals(0, count($recyclebin->get_items()));
-    }
-}
diff --git a/admin/tool/recyclebin/tests/events_test.php b/admin/tool/recyclebin/tests/events_test.php
new file mode 100644 (file)
index 0000000..05d519b
--- /dev/null
@@ -0,0 +1,225 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Events tests.
+ *
+ * @package tool_recyclebin
+ * @category test
+ * @copyright 2016 Mark Nelson <markn@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Events tests class.
+ *
+ * @package tool_recyclebin
+ * @category test
+ * @copyright 2016 Mark Nelson <markn@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class tool_recyclebin_events_testcase extends advanced_testcase {
+
+    /**
+     * Test set up.
+     *
+     * This is executed before running any test in this file.
+     */
+    public function setUp() {
+        $this->resetAfterTest();
+
+        // We want the category and course bin to be enabled.
+        set_config('categorybinenable', 1, 'tool_recyclebin');
+        set_config('coursebinenable', 1, 'tool_recyclebin');
+    }
+
+    /**
+     * Test the category bin item created event.
+     */
+    public function test_category_bin_item_created() {
+        // Create a course.
+        $course = $this->getDataGenerator()->create_course();
+
+        // Trigger and capture the event.
+        $sink = $this->redirectEvents();
+        delete_course($course, false);
+        $events = $sink->get_events();
+        $event = reset($events);
+
+        // Get the item from the recycle bin.
+        $rb = new \tool_recyclebin\category_bin($course->category);
+        $items = $rb->get_items();
+        $item = reset($items);
+
+        // Check that the event contains the expected values.
+        $this->assertInstanceOf('\tooL_recyclebin\event\category_bin_item_created', $event);
+        $this->assertEquals(context_coursecat::instance($course->category), $event->get_context());
+        $this->assertEquals($item->id, $event->objectid);
+        $this->assertEventContextNotUsed($event);
+    }
+
+    /**
+     * Test the category bin item deleted event.
+     */
+    public function test_category_bin_item_deleted() {
+        // Create a course.
+        $course = $this->getDataGenerator()->create_course();
+
+        // Delete the course.
+        delete_course($course, false);
+
+        // Get the item from the recycle bin.
+        $rb = new \tool_recyclebin\category_bin($course->category);
+        $items = $rb->get_items();
+        $item = reset($items);
+
+        // Trigger and capture the event.
+        $sink = $this->redirectEvents();
+        $rb->delete_item($item);
+        $events = $sink->get_events();
+        $this->assertCount(1, $events);
+        $event = reset($events);
+
+        // Check that the event contains the expected values.
+        $this->assertInstanceOf('\tooL_recyclebin\event\category_bin_item_deleted', $event);
+        $this->assertEquals(context_coursecat::instance($course->category), $event->get_context());
+        $this->assertEquals($item->id, $event->objectid);
+        $this->assertEventContextNotUsed($event);
+    }
+
+    /**
+     * Test the category bin item restored event.
+     */
+    public function test_category_bin_item_restored() {
+        // Create a course.
+        $course = $this->getDataGenerator()->create_course();
+
+        // Delete the course.
+        delete_course($course, false);
+
+        // Get the item from the recycle bin.
+        $rb = new \tool_recyclebin\category_bin($course->category);
+        $items = $rb->get_items();
+        $item = reset($items);
+
+        // Trigger and capture the event.
+        $sink = $this->redirectEvents();
+        $rb->restore_item($item);
+        $events = $sink->get_events();
+        $event = $events[6];
+
+        // Check that the event contains the expected values.
+        $this->assertInstanceOf('\tooL_recyclebin\event\category_bin_item_restored', $event);
+        $this->assertEquals(context_coursecat::instance($course->category), $event->get_context());
+        $this->assertEquals($item->id, $event->objectid);
+        $this->assertEventContextNotUsed($event);
+    }
+
+    /**
+     * Test the course bin item created event.
+     */
+    public function test_course_bin_item_created() {
+        // Create a course.
+        $course = $this->getDataGenerator()->create_course();
+
+        // Create the assignment.
+        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
+        $instance = $generator->create_instance(array('course' => $course->id));
+
+        // Trigger and capture the event.
+        $sink = $this->redirectEvents();
+        course_delete_module($instance->cmid);
+        $events = $sink->get_events();
+        $event = reset($events);
+
+        // Get the item from the recycle bin.
+        $rb = new \tool_recyclebin\course_bin($course->id);
+        $items = $rb->get_items();
+        $item = reset($items);
+
+        // Check that the event contains the expected values.
+        $this->assertInstanceOf('\tooL_recyclebin\event\course_bin_item_created', $event);
+        $this->assertEquals(context_course::instance($course->id), $event->get_context());
+        $this->assertEquals($item->id, $event->objectid);
+        $this->assertEventContextNotUsed($event);
+    }
+
+    /**
+     * Test the course bin item deleted event.
+     */
+    public function test_course_bin_item_deleted() {
+        // Create a course.
+        $course = $this->getDataGenerator()->create_course();
+
+        // Create the assignment.
+        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
+        $instance = $generator->create_instance(array('course' => $course->id));
+
+        // Delete the module.
+        course_delete_module($instance->cmid);
+
+        // Get the item from the recycle bin.
+        $rb = new \tool_recyclebin\course_bin($course->id);
+        $items = $rb->get_items();
+        $item = reset($items);
+
+        // Trigger and capture the event.
+        $sink = $this->redirectEvents();
+        $rb->delete_item($item);
+        $events = $sink->get_events();
+        $this->assertCount(1, $events);
+        $event = reset($events);
+
+        // Check that the event contains the expected values.
+        $this->assertInstanceOf('\tooL_recyclebin\event\course_bin_item_deleted', $event);
+        $this->assertEquals(context_course::instance($course->id), $event->get_context());
+        $this->assertEquals($item->id, $event->objectid);
+        $this->assertEventContextNotUsed($event);
+    }
+
+    /**
+     * Test the course bin item restored event.
+     */
+    public function test_course_bin_item_restored() {
+        // Create a course.
+        $course = $this->getDataGenerator()->create_course();
+
+        // Create the assignment.
+        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
+        $instance = $generator->create_instance(array('course' => $course->id));
+
+        course_delete_module($instance->cmid);
+
+        // Get the item from the recycle bin.
+        $rb = new \tool_recyclebin\course_bin($course->id);
+        $items = $rb->get_items();
+        $item = reset($items);
+
+        // Trigger and capture the event.
+        $sink = $this->redirectEvents();
+        $rb->restore_item($item);
+        $events = $sink->get_events();
+        $event = reset($events);
+
+        // Check that the event contains the expected values.
+        $this->assertInstanceOf('\tooL_recyclebin\event\course_bin_item_restored', $event);
+        $this->assertEquals(context_course::instance($course->id), $event->get_context());
+        $this->assertEquals($item->id, $event->objectid);
+        $this->assertEventContextNotUsed($event);
+    }
+}
index 2510de6..b711511 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2016022300; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->version   = 2016030200; // The current plugin version (Date: YYYYMMDDXX).
 $plugin->requires  = 2015111000; // Requires this Moodle version.
 $plugin->component = 'tool_recyclebin'; // Full name of the plugin (used for diagnostics).