// "supportcontact" settingpage
$temp = new admin_settingpage('supportcontact', new lang_string('supportcontact','admin'));
-if (isloggedin()) {
- global $USER;
- $primaryadminemail = $USER->email;
- $primaryadminname = fullname($USER, true);
-
+$primaryadmin = get_admin();
+if ($primaryadmin) {
+ $primaryadminemail = $primaryadmin->email;
+ $primaryadminname = fullname($primaryadmin, true);
} else {
// no defaults during installation - admin user must be created first
$primaryadminemail = NULL;
require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
require_once(__DIR__ . '/../../../lib/behat/behat_field_manager.php');
-use Behat\Behat\Context\Step\Given as Given,
- Behat\Gherkin\Node\TableNode as TableNode,
+use Behat\Gherkin\Node\TableNode as TableNode,
Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException;
/**
if ($options['profile']) {
$profile = $options['profile'];
- if (!isset($CFG->behat_config[$profile])) {
- echo "Invalid profile passed: " . $profile;
+
+ // If profile passed is not set, then exit.
+ if (!isset($CFG->behat_config[$profile]) && !isset($CFG->behat_profiles[$profile]) &&
+ !(isset($options['replace']) && (strpos($options['profile'], $options['replace']) >= 0 ))) {
+ echo "Invalid profile passed: " . $profile . PHP_EOL;
exit(1);
}
+
$extraopts[] = '--profile="' . $profile . '"';
// By default, profile tags will be used.
if (!empty($CFG->behat_config[$profile]['filters']['tags'])) {
$time = round(microtime(true) - $time, 1);
echo "Finished in " . gmdate("G\h i\m s\s", $time) . PHP_EOL . PHP_EOL;
+ksort($exitcodes);
// Print exit info from each run.
-$status = false;
+// Status bits contains pass/fail status of parallel runs.
+$status = 0;
+$processcounter = 0;
foreach ($exitcodes as $exitcode) {
- $status = (bool)$status || (bool)$exitcode;
+ if ($exitcode) {
+ $status |= (1 << $processcounter);
+ }
+ $processcounter++;
}
// Run finished. Show exit code and output from individual process.
$verbose = empty($options['verbose']) ? false : true;
-$verbose = $verbose || $status;
+$verbose = $verbose || !empty($status);
// Show exit code from each process, if any process failed.
if ($verbose) {
// Echo exit codes.
echo "Exit codes for each behat run: " . PHP_EOL;
- ksort($exitcodes);
foreach ($exitcodes as $run => $exitcode) {
echo $run . ": " . $exitcode . PHP_EOL;
}
// Remove site symlink if necessary.
behat_config_manager::drop_parallel_site_links();
-exit((int) $status);
+exit($status);
/**
* Signal handler for terminal exit.
// This is only displayed once for parallel install.
if (empty($options['run'])) {
+ // Notify user that 2.5 profile has been converted to 3.5.
+ if (behat_config_manager::$autoprofileconversion) {
+ mtrace("2.5 behat profile detected, automatically converted to current 3.x format");
+ }
+
$runtestscommand = behat_command::get_behat_command(true, !empty($options['run']));
$runtestscommand .= ' --config ' . behat_config_manager::get_behat_cli_config_filepath();
When I set the field "First name" to "Field value"
And I set the field "Select a country" to "Japan"
And I set the field "Unmask" to "1"
- And I expand all fieldsets
Then the field "First name" matches value "Field value"
And the "Select a country" select box should contain "Japan"
And the field "Unmask" matches value "1"
// YAML decides when is is necessary to wrap strings between single quotes, so not controlled
// values like paths should not be asserted including the key name as they would depend on the
// directories values.
- $this->assertContains($CFG->dirroot . DIRECTORY_SEPARATOR . 'lib' . DIRECTORY_SEPARATOR . 'behat' . DIRECTORY_SEPARATOR . 'features', $contents);
+ $this->assertContains($CFG->dirroot, $contents);
// Not quoted strings.
$this->assertContains('micarro: /me/lo/robaron', $contents);
- $this->assertContains('class: behat_init_context', $contents);
// YAML uses single quotes to wrap URL strings.
$this->assertContains("base_url: '" . $CFG->behat_wwwroot . "'", $contents);
Scenario: Install language pack
Given I log in as "admin"
And I navigate to "Language packs" node in "Site administration > Language"
- When I set the field "Available language packs" to "English - Pirate (en_ar)"
+ When I set the field "Available language packs" to "en_ar"
And I press "Install selected language pack(s)"
Then I should see "Language pack 'en_ar' was successfully installed"
- And the "Installed language packs" select box should contain "English - Pirate (en_ar)"
+ And the "Installed language packs" select box should contain "en_ar"
And I navigate to "Live logs" node in "Site administration > Reports"
And I should see "The language pack 'en_ar' was installed."
And I log out
Scenario: Try to uninstall language pack
Given I log in as "admin"
And I navigate to "Language packs" node in "Site administration > Language"
- And I set the field "Available language packs" to "English - Pirate (en_ar)"
+ And I set the field "Available language packs" to "en_ar"
And I press "Install selected language pack(s)"
- When I set the field "Installed language packs" to "English - Pirate (en_ar)"
+ When I set the field "Installed language packs" to "en_ar"
And I press "Uninstall selected language pack(s)"
And I press "Continue"
Then I should see "Language pack 'en_ar' was uninstalled"
- And the "Installed language packs" select box should not contain "English - Pirate (en_ar)"
- And the "Available language packs" select box should contain "English - Pirate (en_ar)"
+ And the "Installed language packs" select box should not contain "en_ar"
+ And the "Available language packs" select box should contain "en_ar"
And I navigate to "Live logs" node in "Site administration > Reports"
And I should see "The language pack 'en_ar' was removed."
And I should see "Language pack uninstalled"
Scenario: Try to uninstall English language pack
Given I log in as "admin"
And I navigate to "Language packs" node in "Site administration > Language"
- When I set the field "Installed language packs" to "English (en)"
+ When I set the field "Installed language packs" to "en"
And I press "Uninstall selected language pack(s)"
Then I should see "The English language pack cannot be uninstalled."
And I navigate to "Live logs" node in "Site administration > Reports"
--- /dev/null
+<?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/>.
+
+/**
+ * The main interface for recycle bin methods.
+ *
+ * @package tool_recyclebin
+ * @copyright 2015 University of Kent
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_recyclebin;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Represents a recyclebin.
+ *
+ * @package tool_recyclebin
+ * @copyright 2015 University of Kent
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+abstract class base_bin {
+
+ /**
+ * Is this recyclebin enabled?
+ */
+ public static function is_enabled() {
+ return false;
+ }
+
+ /**
+ * Returns an item from the recycle bin.
+ *
+ * @param int $itemid Item ID to retrieve.
+ */
+ public abstract function get_item($itemid);
+
+ /**
+ * Returns a list of items in the recycle bin.
+ */
+ public abstract function get_items();
+
+ /**
+ * Store an item in this recycle bin.
+ *
+ * @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
+ */
+ public abstract function restore_item($item);
+
+ /**
+ * Delete an item from the recycle bin.
+ *
+ * @param \stdClass $item The item database record
+ */
+ public abstract function delete_item($item);
+
+ /**
+ * Empty the recycle bin.
+ */
+ public function delete_all_items() {
+ // Cleanup all items.
+ $items = $this->get_items();
+ foreach ($items as $item) {
+ if ($this->can_delete()) {
+ $this->delete_item($item);
+ }
+ }
+ }
+
+ /**
+ * Can we view items in this recycle bin?
+ */
+ public abstract function can_view();
+
+ /**
+ * Can we restore items in this recycle bin?
+ */
+ public abstract function can_restore();
+
+ /**
+ * Can we delete this?
+ */
+ public abstract function can_delete();
+}
--- /dev/null
+<?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/>.
+
+/**
+ * The main interface for recycle bin methods.
+ *
+ * @package tool_recyclebin
+ * @copyright 2015 University of Kent
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_recyclebin;
+
+defined('MOODLE_INTERNAL') || die();
+
+define('TOOL_RECYCLEBIN_COURSECAT_BIN_FILEAREA', 'recyclebin_coursecat');
+
+/**
+ * Represents a category's recyclebin.
+ *
+ * @package tool_recyclebin
+ * @copyright 2015 University of Kent
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class category_bin extends base_bin {
+
+ /**
+ * @var int The category id.
+ */
+ protected $_categoryid;
+
+ /**
+ * Constructor.
+ *
+ * @param int $categoryid The category id.
+ */
+ public function __construct($categoryid) {
+ $this->_categoryid = $categoryid;
+ }
+
+ /**
+ * Is this recyclebin enabled?
+ *
+ * @return bool true if enabled, false if not.
+ */
+ public static function is_enabled() {
+ return get_config('tool_recyclebin', 'categorybinenable');
+ }
+
+ /**
+ * Returns an item from the recycle bin.
+ *
+ * @param int $itemid Item ID to retrieve.
+ * @return \stdClass the item.
+ */
+ public function get_item($itemid) {
+ global $DB;
+
+ $item = $DB->get_record('tool_recyclebin_category', array(
+ 'id' => $itemid
+ ), '*', MUST_EXIST);
+
+ $item->name = get_course_display_name_for_list($item);
+
+ return $item;
+ }
+
+ /**
+ * 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(
+ 'categoryid' => $this->_categoryid
+ ));
+
+ foreach ($items as $item) {
+ $item->name = get_course_display_name_for_list($item);
+ }
+
+ return $items;
+ }
+
+ /**
+ * Store a course in the recycle bin.
+ *
+ * @param \stdClass $course Course
+ * @throws \moodle_exception
+ */
+ public function store_item($course) {
+ global $CFG, $DB;
+
+ require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
+
+ // Backup the course.
+ $user = get_admin();
+ $controller = new \backup_controller(
+ \backup::TYPE_1COURSE,
+ $course->id,
+ \backup::FORMAT_MOODLE,
+ \backup::INTERACTIVE_NO,
+ \backup::MODE_GENERAL,
+ $user->id
+ );
+ $controller->execute_plan();
+
+ // Grab the result.
+ $result = $controller->get_results();
+ if (!isset($result['backup_destination'])) {
+ throw new \moodle_exception('Failed to backup activity prior to deletion.');
+ }
+
+ // Grab the filename.
+ $file = $result['backup_destination'];
+ if (!$file->get_contenthash()) {
+ throw new \moodle_exception('Failed to backup activity prior to deletion (invalid file).');
+ }
+
+ // Record the activity, get an ID.
+ $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.
+ $fs = get_file_storage();
+ if (!$fs->create_file_from_storedfile($filerecord, $file)) {
+ // Failed, cleanup first.
+ $DB->delete_records('tool_recyclebin_category', array(
+ 'id' => $binid
+ ));
+
+ throw new \moodle_exception("Failed to copy backup file to recyclebin.");
+ }
+
+ // Delete the old file.
+ $file->delete();
+
+ // Fire event.
+ $event = \tool_recyclebin\event\category_bin_item_created::create(array(
+ 'objectid' => $binid,
+ 'context' => \context_coursecat::instance($course->category)
+ ));
+ $event->trigger();
+ }
+
+ /**
+ * Restore an item from the recycle bin.
+ *
+ * @param \stdClass $item The item database record
+ * @throws \moodle_exception
+ */
+ public function restore_item($item) {
+ 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;
+ $course->shortname = $item->shortname;
+ $course->fullname = $item->fullname;
+ $course->summary = '';
+
+ // Create a new course.
+ $course = create_course($course);
+ if (!$course) {
+ throw new \moodle_exception("Could not create course to restore into.");
+ }
+
+ // Define the import.
+ $controller = new \restore_controller(
+ $tempdir,
+ $course->id,
+ \backup::INTERACTIVE_NO,
+ \backup::MODE_GENERAL,
+ $user->id,
+ \backup::TARGET_NEW_COURSE
+ );
+
+ // Prechecks.
+ if (!$controller->execute_precheck()) {
+ $results = $controller->get_precheck_results();
+
+ // Check if errors have been found.
+ if (!empty($results['errors'])) {
+ // Delete the temporary file we created.
+ fulldelete($fulltempdir);
+
+ // 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();
+ }
+ }
+
+ // Run the import.
+ $controller->execute_plan();
+
+ // Fire event.
+ $event = \tool_recyclebin\event\category_bin_item_restored::create(array(
+ 'objectid' => $item->id,
+ 'context' => $context
+ ));
+ $event->add_record_snapshot('tool_recyclebin_category', $item);
+ $event->trigger();
+
+ // Cleanup.
+ fulldelete($fulltempdir);
+ $this->delete_item($item);
+ }
+
+ /**
+ * Delete an item from the recycle bin.
+ *
+ * @param \stdClass $item The item database record
+ * @throws \coding_exception
+ */
+ public function delete_item($item) {
+ global $DB;
+
+ // 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
+ ));
+
+ // Fire event.
+ $event = \tool_recyclebin\event\category_bin_item_deleted::create(array(
+ 'objectid' => $item->id,
+ 'context' => \context_coursecat::instance($item->categoryid)
+ ));
+ $event->add_record_snapshot('tool_recyclebin_category', $item);
+ $event->trigger();
+ }
+
+ /**
+ * Can we view items in this recycle bin?
+ *
+ * @return bool returns true if they can view, false if not
+ */
+ public function can_view() {
+ $context = \context_coursecat::instance($this->_categoryid);
+ return has_capability('tool/recyclebin:viewitems', $context);
+ }
+
+ /**
+ * Can we restore items in this recycle bin?
+ *
+ * @return bool returns true if they can restore, false if not
+ */
+ public function can_restore() {
+ $context = \context_coursecat::instance($this->_categoryid);
+ return has_capability('tool/recyclebin:restoreitems', $context);
+ }
+
+ /**
+ * Can we delete items in this recycle bin?
+ *
+ * @return bool returns true if they can delete, false if not
+ */
+ public function can_delete() {
+ $context = \context_coursecat::instance($this->_categoryid);
+ return has_capability('tool/recyclebin:deleteitems', $context);
+ }
+}
--- /dev/null
+<?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/>.
+
+/**
+ * The main interface for recycle bin methods.
+ *
+ * @package tool_recyclebin
+ * @copyright 2015 University of Kent
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_recyclebin;
+
+defined('MOODLE_INTERNAL') || die();
+
+define('TOOL_RECYCLEBIN_COURSE_BIN_FILEAREA', 'recyclebin_course');
+
+/**
+ * Represents a course's recyclebin.
+ *
+ * @package tool_recyclebin
+ * @copyright 2015 University of Kent
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class course_bin extends base_bin {
+
+ /**
+ * @var int The course id.
+ */
+ protected $_courseid;
+
+ /**
+ * Constructor.
+ *
+ * @param int $courseid Course ID.
+ */
+ public function __construct($courseid) {
+ $this->_courseid = $courseid;
+ }
+
+ /**
+ * Is this recyclebin enabled?
+ *
+ * @return bool true if enabled, false if not.
+ */
+ public static function is_enabled() {
+ return get_config('tool_recyclebin', 'coursebinenable');
+ }
+
+ /**
+ * Returns an item from the recycle bin.
+ *
+ * @param int $itemid Item ID to retrieve.
+ * @return \stdClass the item.
+ */
+ public function get_item($itemid) {
+ global $DB;
+
+ return $DB->get_record('tool_recyclebin_course', array(
+ 'id' => $itemid
+ ), '*', MUST_EXIST);
+ }
+
+ /**
+ * 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(
+ 'courseid' => $this->_courseid
+ ));
+ }
+
+ /**
+ * Store a course module in the recycle bin.
+ *
+ * @param \stdClass $cm Course module
+ * @throws \moodle_exception
+ */
+ public function store_item($cm) {
+ global $CFG, $DB;
+
+ require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
+
+ // Get more information.
+ $modinfo = get_fast_modinfo($cm->course);
+
+ if (!isset($modinfo->cms[$cm->id])) {
+ return; // Can't continue without the module information.
+ }
+
+ $cminfo = $modinfo->cms[$cm->id];
+
+ // Check backup/restore support.
+ if (!plugin_supports('mod', $cminfo->modname , FEATURE_BACKUP_MOODLE2)) {
+ return;
+ }
+
+ // Backup the activity.
+ $user = get_admin();
+ $controller = new \backup_controller(
+ \backup::TYPE_1ACTIVITY,
+ $cm->id,
+ \backup::FORMAT_MOODLE,
+ \backup::INTERACTIVE_NO,
+ \backup::MODE_GENERAL,
+ $user->id
+ );
+ $controller->execute_plan();
+
+ // Grab the result.
+ $result = $controller->get_results();
+ if (!isset($result['backup_destination'])) {
+ throw new \moodle_exception('Failed to backup activity prior to deletion.');
+ }
+
+ // Grab the filename.
+ $file = $result['backup_destination'];
+ if (!$file->get_contenthash()) {
+ throw new \moodle_exception('Failed to backup activity prior to deletion (invalid file).');
+ }
+
+ // Record the activity, get an ID.
+ $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.
+ $fs = get_file_storage();
+ if (!$fs->create_file_from_storedfile($filerecord, $file)) {
+ // Failed, cleanup first.
+ $DB->delete_records('tool_recyclebin_course', array(
+ 'id' => $binid
+ ));
+
+ throw new \moodle_exception("Failed to copy backup file to recyclebin.");
+ }
+
+ // Delete the old file.
+ $file->delete();
+
+ // Fire event.
+ $event = \tool_recyclebin\event\course_bin_item_created::create(array(
+ 'objectid' => $binid,
+ 'context' => \context_course::instance($cm->course)
+ ));
+ $event->trigger();
+ }
+
+ /**
+ * Restore an item from the recycle bin.
+ *
+ * @param \stdClass $item The item database record
+ * @throws \moodle_exception
+ */
+ public function restore_item($item) {
+ global $CFG, $OUTPUT, $PAGE;
+
+ require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
+
+ $user = get_admin();
+
+ // 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!');
+ }
+
+ 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 tempdir.
+ $fb = get_file_packer('application/vnd.moodle.backup');
+ $fb->extract_to_pathname($file, $fulltempdir);
+
+ // Define the import.
+ $controller = new \restore_controller(
+ $tempdir,
+ $this->_courseid,
+ \backup::INTERACTIVE_NO,
+ \backup::MODE_GENERAL,
+ $user->id,
+ \backup::TARGET_EXISTING_ADDING
+ );
+
+ // Prechecks.
+ if (!$controller->execute_precheck()) {
+ $results = $controller->get_precheck_results();
+
+ // If errors are found then delete the file we created.
+ if (!empty($results['errors'])) {
+ fulldelete($fulltempdir);
+
+ 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();
+ }
+ }
+
+ // Run the import.
+ $controller->execute_plan();
+
+ // Fire event.
+ $event = \tool_recyclebin\event\course_bin_item_restored::create(array(
+ 'objectid' => $item->id,
+ 'context' => $context
+ ));
+ $event->add_record_snapshot('tool_recyclebin_course', $item);
+ $event->trigger();
+
+ // Cleanup.
+ fulldelete($fulltempdir);
+ $this->delete_item($item);
+ }
+
+ /**
+ * Delete an item from the recycle bin.
+ *
+ * @param \stdClass $item The item database record
+ */
+ public function delete_item($item) {
+ global $DB;
+
+ // 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
+ ));
+
+ // The course might have been deleted, check we have a context.
+ $context = \context_course::instance($item->courseid, \IGNORE_MISSING);
+ if (!$context) {
+ return;
+ }
+
+ // Fire event.
+ $event = \tool_recyclebin\event\course_bin_item_deleted::create(array(
+ 'objectid' => $item->id,
+ 'context' => $context
+ ));
+ $event->add_record_snapshot('tool_recyclebin_course', $item);
+ $event->trigger();
+ }
+
+ /**
+ * Can we view items in this recycle bin?
+ *
+ * @return bool returns true if they can view, false if not
+ */
+ public function can_view() {
+ $context = \context_course::instance($this->_courseid);
+ return has_capability('tool/recyclebin:viewitems', $context);
+ }
+
+ /**
+ * Can we restore items in this recycle bin?
+ *
+ * @return bool returns true if they can restore, false if not
+ */
+ public function can_restore() {
+ $context = \context_course::instance($this->_courseid);
+ return has_capability('tool/recyclebin:restoreitems', $context);
+ }
+
+ /**
+ * Can we delete this?
+ *
+ * @return bool returns true if they can delete, false if not
+ */
+ public function can_delete() {
+ $context = \context_course::instance($this->_courseid);
+ return has_capability('tool/recyclebin:deleteitems', $context);
+ }
+}
--- /dev/null
+<?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 events.
+ *
+ * @package tool_recyclebin
+ * @copyright 2015 University of Kent
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_recyclebin\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Event class.
+ *
+ * @package tool_recyclebin
+ * @copyright 2015 University of Kent
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class category_bin_item_created extends \core\event\base {
+
+ /**
+ * Init method.
+ */
+ protected function init() {
+ $this->data['objecttable'] = 'tool_recyclebin_category';
+ $this->data['crud'] = 'c';
+ $this->data['edulevel'] = self::LEVEL_OTHER;
+ }
+
+ /**
+ * Returns localised general event name.
+ *
+ * @return string
+ */
+ public static function get_name() {
+ return get_string('eventitemcreated', 'tool_recyclebin');
+ }
+
+ /**
+ * Returns description of what happened.
+ *
+ * @return string
+ */
+ public function get_description() {
+ return get_string('eventitemcreated_desc', 'tool_recyclebin', array(
+ 'objectid' => $this->objectid
+ ));
+ }
+}
--- /dev/null
+<?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 events.
+ *
+ * @package tool_recyclebin
+ * @copyright 2015 University of Kent
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_recyclebin\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Event class.
+ *
+ * @package tool_recyclebin
+ * @copyright 2015 University of Kent
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class category_bin_item_deleted extends \core\event\base {
+
+ /**
+ * Init method.
+ */
+ protected function init() {
+ $this->data['objecttable'] = 'tool_recyclebin_category';
+ $this->data['crud'] = 'd';
+ $this->data['edulevel'] = self::LEVEL_OTHER;
+ }
+
+ /**
+ * Returns localised general event name.
+ *
+ * @return string
+ */
+ public static function get_name() {
+ return get_string('eventitemdeleted', 'tool_recyclebin');
+ }
+
+ /**
+ * Returns description of what happened.
+ *
+ * @return string
+ */
+ public function get_description() {
+ return get_string('eventitemdeleted_desc', 'tool_recyclebin', array(
+ 'objectid' => $this->objectid
+ ));
+ }
+}
--- /dev/null
+<?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 events.
+ *
+ * @package tool_recyclebin
+ * @copyright 2015 University of Kent
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_recyclebin\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Event Class
+ *
+ * @package tool_recyclebin
+ * @copyright 2015 University of Kent
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class category_bin_item_restored extends \core\event\base {
+
+ /**
+ * Init method.
+ */
+ protected function init() {
+ $this->data['objecttable'] = 'tool_recyclebin_category';
+ $this->data['crud'] = 'u';
+ $this->data['edulevel'] = self::LEVEL_OTHER;
+ }
+
+ /**
+ * Returns localised general event name.
+ *
+ * @return string
+ */
+ public static function get_name() {
+ return get_string('eventitemrestored', 'tool_recyclebin');
+ }
+
+ /**
+ * Returns description of what happened.
+ *
+ * @return string
+ */
+ public function get_description() {
+ return get_string('eventitemrestored_desc', 'tool_recyclebin', array(
+ 'objectid' => $this->objectid
+ ));
+ }
+}
--- /dev/null
+<?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 events.
+ *
+ * @package tool_recyclebin
+ * @copyright 2015 University of Kent
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_recyclebin\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * 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_bin_item_created extends \core\event\base {
+
+ /**
+ * Init method.
+ */
+ protected function init() {
+ $this->data['objecttable'] = 'tool_recyclebin_course';
+ $this->data['crud'] = 'c';
+ $this->data['edulevel'] = self::LEVEL_OTHER;
+ }
+
+ /**
+ * Returns localised general event name.
+ *
+ * @return string
+ */
+ public static function get_name() {
+ return get_string('eventitemcreated', 'tool_recyclebin');
+ }
+
+ /**
+ * Returns description of what happened.
+ *
+ * @return string
+ */
+ public function get_description() {
+ return get_string('eventitemcreated_desc', 'tool_recyclebin', array(
+ 'objectid' => $this->objectid
+ ));
+ }
+}
--- /dev/null
+<?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 events.
+ *
+ * @package tool_recyclebin
+ * @copyright 2015 University of Kent
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_recyclebin\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * 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_bin_item_deleted extends \core\event\base {
+
+ /**
+ * Init method.
+ */
+ protected function init() {
+ $this->data['objecttable'] = 'tool_recyclebin_course';
+ $this->data['crud'] = 'd';
+ $this->data['edulevel'] = self::LEVEL_OTHER;
+ }
+
+ /**
+ * Returns localised general event name.
+ *
+ * @return string
+ */
+ public static function get_name() {
+ return get_string('eventitemdeleted', 'tool_recyclebin');
+ }
+
+ /**
+ * Returns description of what happened.
+ *
+ * @return string
+ */
+ public function get_description() {
+ return get_string('eventitemdeleted_desc', 'tool_recyclebin', array(
+ 'objectid' => $this->objectid
+ ));
+ }
+}
--- /dev/null
+<?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 events.
+ *
+ * @package tool_recyclebin
+ * @copyright 2015 University of Kent
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_recyclebin\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * 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_bin_item_restored extends \core\event\base {
+
+ /**
+ * Init method.
+ */
+ protected function init() {
+ $this->data['objecttable'] = 'tool_recyclebin_course';
+ $this->data['crud'] = 'u';
+ $this->data['edulevel'] = self::LEVEL_OTHER;
+ }
+
+ /**
+ * Returns localised general event name.
+ *
+ * @return string
+ */
+ public static function get_name() {
+ return get_string('eventitemrestored', 'tool_recyclebin');
+ }
+
+ /**
+ * Returns description of what happened.
+ *
+ * @return string
+ */
+ public function get_description() {
+ return get_string('eventitemrestored_desc', 'tool_recyclebin', array(
+ 'objectid' => $this->objectid
+ ));
+ }
+}
--- /dev/null
+<?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 cron task.
+ *
+ * @package tool_recyclebin
+ * @copyright 2015 University of Kent
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_recyclebin\task;
+
+/**
+ * This task deletes expired category recyclebin items.
+ *
+ * @package tool_recyclebin
+ * @copyright 2015 University of Kent
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class cleanup_category_bin extends \core\task\scheduled_task {
+
+ /**
+ * Task name.
+ */
+ public function get_name() {
+ return get_string('taskcleanupcategorybin', 'tool_recyclebin');
+ }
+
+ /**
+ * Delete all expired items.
+ */
+ public function execute() {
+ global $DB;
+
+ // 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;
+ }
+
+ // 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("[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();
+
+ return true;
+ }
+}
--- /dev/null
+<?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 cron task.
+ *
+ * @package tool_recyclebin
+ * @copyright 2015 University of Kent
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_recyclebin\task;
+
+/**
+ * This task deletes expired course recyclebin items.
+ *
+ * @package tool_recyclebin
+ * @copyright 2015 University of Kent
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class cleanup_course_bin extends \core\task\scheduled_task {
+
+ /**
+ * Task name.
+ */
+ public function get_name() {
+ return get_string('taskcleanupcoursebin', 'tool_recyclebin');
+ }
+
+ /**
+ * Delete all expired items.
+ */
+ public function execute() {
+ global $DB;
+
+ // 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;
+ }
+
+ // 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("[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();
+
+ return true;
+ }
+}
--- /dev/null
+<?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/>.
+
+/**
+ * Plugin capabilities.
+ *
+ * @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();
+
+$capabilities = array(
+
+ 'tool/recyclebin:deleteitems' => array(
+ 'captype' => 'write',
+ 'riskbitmask' => RISK_DATALOSS,
+ 'contextlevel' => CONTEXT_COURSE,
+ 'archetypes' => array(
+ 'editingteacher' => CAP_ALLOW,
+ 'manager' => CAP_ALLOW
+ )
+ ),
+
+ 'tool/recyclebin:restoreitems' => array(
+ 'captype' => 'write',
+ 'contextlevel' => CONTEXT_COURSE,
+ 'archetypes' => array(
+ 'editingteacher' => CAP_ALLOW,
+ 'manager' => CAP_ALLOW
+ )
+ ),
+
+ 'tool/recyclebin:viewitems' => array(
+ 'captype' => 'read',
+ 'contextlevel' => CONTEXT_COURSE,
+ 'archetypes' => array(
+ 'teacher' => CAP_ALLOW,
+ 'editingteacher' => CAP_ALLOW,
+ 'manager' => CAP_ALLOW
+ )
+ )
+);
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" ?>
+<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 items in the course recycle bin">
+ <FIELDS>
+ <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
+ <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="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="courseid" UNIQUE="false" FIELDS="courseid"/>
+ <INDEX NAME="timecreated" UNIQUE="false" FIELDS="timecreated"/>
+ </INDEXES>
+ </TABLE>
+ <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="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="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="categoryid" UNIQUE="false" FIELDS="categoryid"/>
+ <INDEX NAME="timecreated" UNIQUE="false" FIELDS="timecreated"/>
+ </INDEXES>
+ </TABLE>
+ </TABLES>
+</XMLDB>
\ No newline at end of file
--- /dev/null
+<?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 tasks.
+ *
+ * @package tool_recyclebin
+ * @copyright 2015 University of Kent
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+$tasks = array(
+ array(
+ 'classname' => 'tool_recyclebin\task\cleanup_course_bin',
+ 'blocking' => 0,
+ 'minute' => '*/30',
+ 'hour' => '*',
+ 'day' => '*',
+ 'dayofweek' => '*',
+ 'month' => '*'
+ ),
+ array(
+ 'classname' => 'tool_recyclebin\task\cleanup_category_bin',
+ 'blocking' => 0,
+ 'minute' => '*/30',
+ 'hour' => '*',
+ 'day' => '*',
+ 'dayofweek' => '*',
+ 'month' => '*'
+ )
+);
--- /dev/null
+<?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/>.
+
+/**
+ * This page shows the contents of a recyclebin for a given course.
+ *
+ * @package tool_recyclebin
+ * @copyright 2015 University of Kent
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once(dirname(__FILE__) . '/../../../config.php');
+require_once($CFG->libdir . '/tablelib.php');
+
+$contextid = required_param('contextid', PARAM_INT);
+$action = optional_param('action', null, PARAM_ALPHA);
+
+$context = context::instance_by_id($contextid, MUST_EXIST);
+$PAGE->set_context($context);
+
+// We could be a course or a category.
+switch ($context->contextlevel) {
+ case CONTEXT_COURSE:
+ require_login($context->instanceid);
+
+ $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:
+ require_login();
+
+ $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:
+ print_error('invalidcontext', 'tool_recyclebin');
+ break;
+}
+
+if (!$recyclebin::is_enabled()) {
+ print_error('notenabled', 'tool_recyclebin');
+}
+
+$PAGE->set_url('/admin/tool/recyclebin/index.php', array(
+ 'contextid' => $contextid
+));
+$PAGE->set_title(get_string('pluginname', 'tool_recyclebin'));
+
+// If we are doing anything, we need a sesskey!
+if (!empty($action)) {
+ raise_memory_limit(MEMORY_EXTRA);
+ require_sesskey();
+
+ $item = null;
+ if ($action == 'restore' || $action == 'delete') {
+ $itemid = required_param('itemid', PARAM_INT);
+ $item = $recyclebin->get_item($itemid);
+ }
+
+ switch ($action) {
+ // Restore it.
+ case 'restore':
+ if ($recyclebin->can_restore()) {
+ $recyclebin->restore_item($item);
+ redirect($PAGE->url, get_string('alertrestored', 'tool_recyclebin', $item), 2);
+ } else {
+ print_error('nopermissions', 'error');
+ }
+ break;
+
+ // Delete it.
+ case 'delete':
+ if ($recyclebin->can_delete()) {
+ $recyclebin->delete_item($item);
+ redirect($PAGE->url, get_string('alertdeleted', 'tool_recyclebin', $item), 2);
+ } else {
+ print_error('nopermissions', 'error');
+ }
+ break;
+
+ // Empty it.
+ case 'empty':
+ $recyclebin->delete_all_items();
+ redirect($PAGE->url, get_string('alertemptied', 'tool_recyclebin'), 2);
+ break;
+ }
+}
+
+// Add a "Go Back" button.
+$goback = html_writer::start_tag('div', array('class' => 'backlink'));
+$goback .= html_writer::link($context->get_url(), get_string('backto', '', $context->get_context_name()));
+$goback .= html_writer::end_tag('div');
+
+// Output header.
+echo $OUTPUT->header();
+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('noitemsinbin', 'tool_recyclebin'));
+ echo $goback;
+ echo $OUTPUT->footer();
+ die;
+}
+
+// Start with a description.
+if ($expiry > 0) {
+ $expirydisplay = format_time($expiry);
+ echo '<div class=\'alert\'>' . get_string('deleteexpirywarning', 'tool_recyclebin', $expirydisplay) . '</div>';
+}
+
+// Define columns and headers.
+$firstcolstr = $context->contextlevel == CONTEXT_COURSE ? 'activity' : 'course';
+$columns = array($firstcolstr, 'date', 'restore', 'delete');
+$headers = array(
+ get_string($firstcolstr),
+ get_string('datedeleted', 'tool_recyclebin'),
+ get_string('restore'),
+ get_string('delete')
+);
+
+// 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');
+$table->setup();
+
+// Cache a list of modules.
+$modules = null;
+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 (isset($modules[$item->module])) {
+ $mod = $modules[$item->module];
+ $modname = get_string('modulename', $mod->name);
+ $name = '<img src="' . $OUTPUT->pix_url('icon', $mod->name) . '" class="icon" alt="' . $modname . '" /> ' . $name;
+ }
+ }
+
+ $row[] = $name;
+ $row[] = userdate($item->timecreated);
+
+ // Build restore link.
+ if ($canrestore && ($context->contextlevel == CONTEXT_COURSECAT || isset($modules[$item->module]))) {
+ $restoreurl = new moodle_url($PAGE->url, array(
+ 'contextid' => $contextid,
+ 'itemid' => $item->id,
+ 'action' => 'restore',
+ 'sesskey' => sesskey()
+ ));
+ $row[] = $OUTPUT->action_icon($restoreurl, new pix_icon('t/restore', get_string('restore'), '', array(
+ 'class' => 'iconsmall'
+ )));
+ } else {
+ // Show padlock.
+ $row[] = $OUTPUT->pix_icon('t/locked', get_string('locked', 'admin'), '', array('class' => 'iconsmall'));
+ }
+
+ // Build delete link.
+ if ($recyclebin->can_delete()) {
+ $showempty = true;
+ $delete = new moodle_url($PAGE->url, array(
+ 'contextid' => $contextid,
+ 'itemid' => $item->id,
+ 'action' => 'delete',
+ 'sesskey' => sesskey()
+ ));
+ $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 {
+ // Show padlock.
+ $row[] = $OUTPUT->pix_icon('t/locked', get_string('locked', 'admin'), '', array('class' => 'iconsmall'));
+ }
+
+ $table->add_data($row);
+}
+
+// Display the table now.
+$table->finish_output();
+
+// Empty recyclebin link.
+if ($showempty) {
+ $emptylink = new moodle_url($PAGE->url, array(
+ 'contextid' => $contextid,
+ 'action' => 'empty',
+ 'sesskey' => sesskey()
+ ));
+ $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('deleteallconfirm', 'deleteconfirm'), 'tool_recyclebin');
+
+// Output footer.
+echo $OUTPUT->footer();
--- /dev/null
+<?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/>.
+
+/**
+ * English strings for tool_recyclebin.
+ *
+ * @package tool_recyclebin
+ * @copyright 2015 Skylar Kelty <S.Kelty@kent.ac.uk>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$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['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';
--- /dev/null
+<?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/>.
+
+/**
+ * Local lib code
+ *
+ * @package tool_recyclebin
+ * @copyright 2015 Skylar Kelty <S.Kelty@kent.ac.uk>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+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
+ * @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;
+
+ // Only add this settings item on non-site course pages.
+ 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 (!$coursebin->can_view()) {
+ return null;
+ }
+
+ $url = null;
+ $settingnode = null;
+
+ $url = new moodle_url('/admin/tool/recyclebin/index.php', array(
+ 'contextid' => $context->id
+ ));
+
+ // If we are set to auto-hide, check the number of items.
+ $autohide = get_config('tool_recyclebin', 'autohide');
+ if ($autohide) {
+ $items = $coursebin->get_items();
+ if (empty($items)) {
+ return null;
+ }
+ }
+
+ // Add the recyclebin link.
+ $pluginname = get_string('pluginname', 'tool_recyclebin');
+
+ $node = navigation_node::create(
+ $pluginname,
+ $url,
+ navigation_node::NODETYPE_LEAF,
+ 'tool_recyclebin',
+ 'tool_recyclebin',
+ new pix_icon('trash', $pluginname, 'tool_recyclebin')
+ );
+
+ if ($PAGE->url->compare($url, URL_MATCH_BASE)) {
+ $node->make_active();
+ }
+
+ $navigation->add_node($node);
+}
+
+/**
+ * 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
+ * @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;
+
+ // 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 (!$categorybin->can_view()) {
+ return null;
+ }
+
+ $url = null;
+ $settingnode = null;
+
+ // Add a link to the category recyclebin.
+ $url = new moodle_url('/admin/tool/recyclebin/index.php', array(
+ 'contextid' => $context->id
+ ));
+
+ // If we are set to auto-hide, check the number of items.
+ $autohide = get_config('tool_recyclebin', 'autohide');
+ if ($autohide) {
+ $items = $categorybin->get_items();
+ if (empty($items)) {
+ return null;
+ }
+ }
+
+ // Add the recyclebin link.
+ $pluginname = get_string('pluginname', 'tool_recyclebin');
+
+ $node = navigation_node::create(
+ $pluginname,
+ $url,
+ navigation_node::NODETYPE_LEAF,
+ 'tool_recyclebin',
+ 'tool_recyclebin',
+ new pix_icon('trash', $pluginname, 'tool_recyclebin')
+ );
+
+ if ($PAGE->url->compare($url, URL_MATCH_BASE)) {
+ $node->make_active();
+ }
+
+ $navigation->add_node($node);
+}
+
+/**
+ * Hook called before we delete a course module.
+ *
+ * @param \stdClass $cm The course module record.
+ */
+function tool_recyclebin_pre_course_module_delete($cm) {
+ if (\tool_recyclebin\course_bin::is_enabled()) {
+ $coursebin = new \tool_recyclebin\course_bin($cm->course);
+ $coursebin->store_item($cm);
+ }
+}
+
+/**
+ * Hook called before we delete a course.
+ *
+ * @param \stdClass $course The course record.
+ */
+function tool_recyclebin_pre_course_delete($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();
+}
--- /dev/null
+<?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
--- /dev/null
+<?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 settings.
+ *
+ * @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();
+
+global $PAGE;
+
+if ($hassiteconfig) {
+ $settings = new admin_settingpage('tool_recyclebin', get_string('pluginname', 'tool_recyclebin'));
+ $ADMIN->add('tools', $settings);
+
+ $settings->add(new admin_setting_configcheckbox(
+ 'tool_recyclebin/coursebinenable',
+ new lang_string('coursebinenable', 'tool_recyclebin'),
+ '',
+ 1
+ ));
+
+ $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/categorybinenable',
+ new lang_string('categorybinenable', 'tool_recyclebin'),
+ '',
+ 1
+ ));
+
+ $settings->add(new admin_setting_configduration(
+ 'tool_recyclebin/categorybinexpiry',
+ new lang_string('categorybinexpiry', 'tool_recyclebin'),
+ new lang_string('categorybinexpiry_desc', 'tool_recyclebin'),
+ WEEKSECS
+ ));
+
+ $settings->add(new admin_setting_configcheckbox(
+ 'tool_recyclebin/autohide',
+ new lang_string('autohide', 'tool_recyclebin'),
+ new lang_string('autohide_desc', 'tool_recyclebin'),
+ 1
+ ));
+}
--- /dev/null
+@tool @tool_recyclebin
+Feature: Backup user data
+ As a teacher
+ I want user data to be backed up when I delete a course module
+ So that I can recover student content
+
+ Background: Course with teacher and student exist.
+ Given the following "users" exist:
+ | username | firstname | lastname | email |
+ | teacher1 | Teacher | 1 | teacher@asd.com |
+ | student1 | Student | 1 | student@asd.com |
+ And the following "courses" exist:
+ | fullname | shortname |
+ | Course 1 | C1 |
+ And the following "course enrolments" exist:
+ | 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 with user data
+ Given I log in as "teacher1"
+ And I follow "Course 1"
+ And I turn editing mode on
+ And I add a "Quiz" to section "1" and I fill the form with:
+ | Name | Quiz 1 |
+ | Description | Test quiz description |
+ And I add a "True/False" question to the "Quiz 1" quiz with:
+ | Question name | TF1 |
+ | Question text | First question |
+ | General feedback | Thank you, this is the general feedback |
+ | Correct answer | False |
+ | 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 |
+ | General feedback | Thank you, this is the general feedback |
+ | Correct answer | False |
+ | Feedback for the response 'True'. | So you think it is true |
+ | Feedback for the response 'False'. | So you think it is false |
+ And I log out
+ And I log in as "student1"
+ And I follow "Course 1"
+ And I follow "Quiz 1"
+ 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 "Finish attempt"
+ And I press "Submit all and finish"
+ And I click on "Submit all and finish" "button" in the "Confirmation" "dialogue"
+ 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"
+ 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"
--- /dev/null
+@tool @tool_recyclebin
+Feature: Basic recycle bin functionality
+ As a teacher
+ 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:
+ | username | firstname | lastname | email |
+ | teacher1 | Teacher | 1 | teacher@asd.com |
+ | student1 | Student | 1 | student@asd.com |
+ 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 a deleted assignment
+ 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
+ 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"
+ 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"
+ 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"
+ 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."
--- /dev/null
+<?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);
+ }
+}
--- /dev/null
+<?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);
+ }
+}
--- /dev/null
+<?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);
+ }
+}
--- /dev/null
+<?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/>.
+
+/**
+ * Defines the version.
+ *
+ * @package tool_recyclebin
+ * @copyright 2016 Skylar Kelty <S.Kelty@kent.ac.uk>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$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).
}
if ($existinguser->$column !== $user->$column) {
if ($column === 'email') {
- if ($DB->record_exists('user', array('email'=>$user->email))) {
- if ($noemailduplicates) {
+ $select = $DB->sql_like('email', ':email', false, true, false, '|');
+ $params = array('email' => $DB->sql_like_escape($user->email, '|'));
+ if ($DB->record_exists_select('user', $select , $params)) {
+
+ $changeincase = core_text::strtolower($existinguser->$column) === core_text::strtolower(
+ $user->$column);
+
+ if ($changeincase) {
+ // If only case is different then switch to lower case and carry on.
+ $user->$column = core_text::strtolower($user->$column);
+ continue;
+ } else if ($noemailduplicates) {
$upt->track('email', $stremailduplicate, 'error');
$upt->track('status', $strusernotupdated, 'error');
$userserrors++;
if (!validate_email($rowcols['email'])) {
$rowcols['status'][] = get_string('invalidemail');
}
- if ($DB->record_exists('user', array('email'=>$rowcols['email']))) {
+
+ $select = $DB->sql_like('email', ':email', false, true, false, '|');
+ $params = array('email' => $DB->sql_like_escape($rowcols['email'], '|'));
+ if ($DB->record_exists_select('user', $select , $params)) {
$rowcols['status'][] = $stremailduplicate;
}
}
}
}
}
+ if ($this->config->suspended_attribute && $this->config->sync_suspended) {
+ $updatekeys[] = 'suspended';
+ }
unset($all_keys); unset($key);
} else {
// get_userinfo_asobj() might have replaced $user->username with the value
// from the LDAP server (which can be mixed-case). Make sure it's lowercase
$user->username = trim(core_text::strtolower($user->username));
+ // It isn't possible to just rely on the configured suspension attribute since
+ // things like active directory use bit masks, other things using LDAP might
+ // do different stuff as well.
+ $user->suspended = $this->is_user_suspended($user);
if (empty($user->lang)) {
$user->lang = $CFG->lang;
}
if (!empty($updatekeys)) {
$newuser = new stdClass();
$newuser->id = $userid;
+ $newuser->suspended = $this->is_user_suspended((object) $newinfo);
foreach ($updatekeys as $key) {
if (isset($newinfo[$key])) {
}
}
$moodleattributes['username'] = core_text::strtolower(trim($this->config->user_attribute));
+ $moodleattributes['suspended'] = core_text::strtolower(trim($this->config->suspended_attribute));
return $moodleattributes;
}
if (!isset($config->user_attribute)) {
$config->user_attribute = '';
}
+ if (!isset($config->suspended_attribute)) {
+ $config->suspended_attribute = '';
+ }
+ if (!isset($config->sync_suspended)) {
+ $config->sync_suspended = false;
+ }
if (!isset($config->search_sub)) {
$config->search_sub = '';
}
set_config('contexts', $config->contexts, $this->pluginconfig);
set_config('user_type', core_text::strtolower(trim($config->user_type)), $this->pluginconfig);
set_config('user_attribute', core_text::strtolower(trim($config->user_attribute)), $this->pluginconfig);
+ set_config('suspended_attribute', core_text::strtolower(trim($config->suspended_attribute)), $this->pluginconfig);
+ set_config('sync_suspended', $config->sync_suspended, $this->pluginconfig);
set_config('search_sub', $config->search_sub, $this->pluginconfig);
set_config('opt_deref', $config->opt_deref, $this->pluginconfig);
set_config('preventpassindb', $config->preventpassindb, $this->pluginconfig);
return false;
}
+ /**
+ * Check if a user is suspended. This function is intended to be used after calling
+ * get_userinfo_asobj. This is needed because LDAP doesn't have a notion of disabled
+ * users, however things like MS Active Directory support it and expose information
+ * through a field.
+ *
+ * @param object $user the user object returned by get_userinfo_asobj
+ * @return boolean
+ */
+ protected function is_user_suspended($user) {
+ if (!$this->config->suspended_attribute || !isset($user->suspended)) {
+ return false;
+ }
+ if ($this->config->suspended_attribute == 'useraccountcontrol' && $this->config->user_type == 'ad') {
+ return (bool)($user->suspended & AUTH_AD_ACCOUNTDISABLE);
+ }
+
+ return (bool)$user->suspended;
+ }
+
} // End of the class
if (!isset($config->user_attribute)) {
$config->user_attribute = '';
}
+if (!isset($config->suspended_attribute)) {
+ $config->suspended_attribute = '';
+}
+if (!isset($config->sync_suspended)) {
+ $config->sync_suspended = '';
+}
if (!isset($config->search_sub)) {
$config->search_sub = '';
}
<?php print_string('auth_ldap_user_attribute', 'auth_ldap') ?>
</td>
</tr>
+<tr valign="top" class="required">
+ <td align="right">
+ <label for="suspended_attribute"><?php print_string('auth_ldap_suspended_attribute_key', 'auth_ldap') ?></label>
+ </td>
+ <td>
+ <input name="suspended_attribute" id="suspended_attribute" type="text" size="30" value="<?php echo $config->suspended_attribute?>" />
+ <?php if (isset($err['suspended_attribute'])) { echo $OUTPUT->error_text($err['suspended_attribute']); } ?>
+ </td>
+ <td>
+ <?php print_string('auth_ldap_suspended_attribute', 'auth_ldap') ?>
+ </td>
+</tr>
<tr valign="top" class="required">
<td align="right">
<label for="memberattribute"><?php print_string('auth_ldap_memberattribute_key', 'auth_ldap') ?></label>
<?php print_string('auth_remove_user', 'auth') ?>
</td>
</tr>
+<tr valign="top">
+ <td align="right">
+ <label for="menusync_suspended"><?php print_string('auth_sync_suspended_key', 'auth') ?></label>
+ </td>
+ <td>
+ <?php echo html_writer::select($yesno, 'sync_suspended', $config->sync_suspended, false); ?>
+ </td>
+ <td>
+ <?php print_string('auth_sync_suspended', 'auth'); ?>
+ </td>
+</tr>
<tr>
<td colspan="2">
<h4><?php print_string('auth_ntlmsso', 'auth_ldap') ?></h4>
$string['auth_ldap_update_userinfo'] = 'Update user information (firstname, lastname, address..) from LDAP to Moodle. Specify "Data mapping" settings as you need.';
$string['auth_ldap_user_attribute'] = 'Optional: Overrides the attribute used to name/search users. Usually \'cn\'.';
$string['auth_ldap_user_attribute_key'] = 'User attribute';
+$string['auth_ldap_suspended_attribute'] = 'Optional: When provided this attribute will be used to enable/suspend the locally created user account.';
+$string['auth_ldap_suspended_attribute_key'] = 'Suspended attribute';
$string['auth_ldap_user_exists'] = 'LDAP username already exists.';
$string['auth_ldap_user_settings'] = 'User lookup settings';
$string['auth_ldap_user_type'] = 'Select how users are stored in LDAP. This setting also specifies how login expiration, grace logins and user creation will work.';
--- /dev/null
+@auth @auth_manual
+Feature: Test manual authentication works.
+ In order to check manual authentication
+ As a teacher
+ I need to go on login page and enter username and password.
+
+ Background:
+ Given the following "users" exist:
+ | username |
+ | teacher1 |
+
+ @javascript
+ Scenario: Check login works with javascript.
+ Given I am on homepage
+ And I expand navigation bar
+ And I click on "Log in" "link" in the ".logininfo" "css_element"
+ When I set the field "Username" to "teacher1"
+ And I set the field "Password" to "teacher1"
+ When I press "Log in"
+ Then I should see "You are logged in as"
+
+ Scenario: Check login works without javascript.
+ Given I am on homepage
+ And I click on "Log in" "link" in the ".logininfo" "css_element"
+ When I set the field "Username" to "teacher1"
+ And I set the field "Password" to "teacher1"
+ When I press "Log in"
+ Then I should see "You are logged in as"
require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
-use Behat\Behat\Context\Step\Given as Given;
-use Behat\Behat\Context\Step\When as When;
+use Moodle\BehatExtension\Context\Step\Given as Given;
+use Moodle\BehatExtension\Context\Step\When as When;
/**
* Log in log out steps definitions.
* @Given /^I log in as "(?P<username_string>(?:[^"]|\\")*)"$/
*/
public function i_log_in_as($username) {
+ // Visit login page.
+ $this->getSession()->visit($this->locate_path('login/index.php'));
- // Running this step using the API rather than a chained step because
- // we need to see if the 'Log in' link is available or we need to click
- // the dropdown to expand the navigation bar before.
- $this->getSession()->visit($this->locate_path('/'));
+ // Enter username and password.
+ $behatforms = behat_context_helper::get('behat_forms');
+ $behatforms->i_set_the_field_to('Username', $this->escape($username));
+ $behatforms->i_set_the_field_to('Password', $this->escape($username));
- // Generic steps (we will prefix them later expanding the navigation dropdown if necessary).
- $steps = array(
- new Given('I click on "' . get_string('login') . '" "link" in the ".logininfo" "css_element"'),
- new Given('I set the field "' . get_string('username') . '" to "' . $this->escape($username) . '"'),
- new Given('I set the field "' . get_string('password') . '" to "'. $this->escape($username) . '"'),
- new Given('I press "' . get_string('login') . '"')
- );
-
- // If Javascript is disabled we have enough with these steps.
- if (!$this->running_javascript()) {
- return $steps;
- }
-
- // Wait for the homepage to be ready.
- $this->getSession()->wait(self::TIMEOUT * 1000, self::PAGE_READY_JS);
-
- // If it is needed, it expands the navigation bar with the 'Log in' link.
- if ($clicknavbar = $this->get_expand_navbar_step()) {
- array_unshift($steps, $clicknavbar);
- }
-
- return $steps;
+ // Press log in button.
+ $behatforms->press_button(get_string('login'));
}
/**
return $steps;
}
-
- /**
- * Returns a step to open the navigation bar if it is needed.
- *
- * The top log in and log out links are hidden when middle or small
- * size windows (or devices) are used. This step returns a step definition
- * clicking to expand the navbar if it is hidden.
- *
- * @return Given|bool A step definition or false if there is no need to show the navbar.
- */
- protected function get_expand_navbar_step() {
-
- // Checking if we need to click the navbar button to show the navigation menu, it
- // is hidden by default when using clean theme and a medium or small screen size.
-
- // The DOM and the JS should be all ready and loaded. Running without spinning
- // as this is a widely used step and we can not spend time here trying to see
- // a DOM node that is not always there (at the moment clean is not even the
- // default theme...).
- $navbuttonjs = "return (
- Y.one('.btn-navbar') &&
- Y.one('.btn-navbar').getComputedStyle('display') !== 'none'
- )";
-
- // Adding an extra click we need to show the 'Log in' link.
- if (!$this->getSession()->getDriver()->evaluateScript($navbuttonjs)) {
- return false;
- }
-
- return new Given('I click on ".btn-navbar" "css_element"');
- }
}
"/descendant::div[@class='restore-course-search']" .
"/descendant::tr[contains(., $existingcourse)]" .
"/descendant::input[@type='radio']");
- $radionode->check();
$radionode->click();
// Pressing the continue button of the restore into an existing course section.
$radionode = $this->find('xpath', "//div[contains(concat(' ', normalize-space(@class), ' '), ' bcs-new-course ')]" .
"/descendant::div[@class='restore-course-search']" .
"/descendant::input[@type='radio']");
- $radionode->check();
$radionode->click();
// Pressing the continue button of the restore into an existing course section.
// Merge without deleting radio option.
$radionode = $this->find('xpath', "//div[contains(concat(' ', normalize-space(@class), ' '), 'bcs-current-course')]" .
"/descendant::input[@type='radio'][@name='target'][@value='1']");
- $radionode->check();
$radionode->click();
// Pressing the continue button of the restore merging section.
// Delete contents radio option.
$radionode = $this->find('xpath', "//div[contains(concat(' ', normalize-space(@class), ' '), 'bcs-current-course')]" .
"/descendant::input[@type='radio'][@name='target'][@value='0']");
- $radionode->check();
$radionode->click();
// Pressing the continue button of the restore merging section.
return;
}
- $pageoptions = clone $options;
-
$rows = $options->getRows();
$newrows = array();
foreach ($rows as $k => $data) {
$newrows[] = $data;
}
}
- $pageoptions->setRows($newrows);
+ $pageoptions = new TableNode($newrows);
+
return $pageoptions;
}
default:
- paths:
- features: lib/behat/features
- bootstrap: lib/behat/features/bootstrap
- context:
- class: behat_init_context
+ suites:
+ default:
+ paths: { }
+ contexts: { }
extensions:
- Behat\MinkExtension\Extension:
+ Behat\MinkExtension:
base_url: 'http://localhost:8000'
goutte: null
selenium2: null
- Moodle\BehatExtension\Extension:
- features: { }
+ Moodle\BehatExtension:
+ moodledirroot: /Should/Change/To/Moodle/www/dir
steps_definitions: { }
require_once($CFG->dirroot.'/course/lib.php');
$context = context_course::instance($course->id);
$isediting = $this->page->user_is_editing() && has_capability('moodle/course:manageactivities', $context);
+ $courserenderer = $this->page->get_renderer('core', 'course');
/// extra fast view mode
if (!$isediting) {
}
if (!empty($cm->url)) {
- $attrs = array();
- $attrs['title'] = $cm->modfullname;
- $attrs['class'] = $cm->extraclasses . ' activity-action';
- if ($cm->onclick) {
- // Get on-click attribute value if specified and decode the onclick - it
- // has already been encoded for display.
- $attrs['onclick'] = htmlspecialchars_decode($cm->onclick);
- }
- if (!$cm->visible) {
- $attrs['class'] .= ' dimmed';
- }
- $icon = '<img src="' . $cm->get_icon_url() . '" class="icon" alt="" />';
- $content = html_writer::link($cm->url, $icon . $cm->get_formatted_name(), $attrs);
+ $content = html_writer::div($courserenderer->course_section_cm_name($cm), 'activity');
} else {
$content = $cm->get_formatted_content(array('overflowdiv' => true, 'noclean' => true));
}
- $this->content->items[] = $indent.html_writer::div($content, 'main-menu-content');
+ $this->content->items[] = $indent . $content;
}
}
return $this->content;
}
// Slow & hacky editing mode.
- /** @var core_course_renderer $courserenderer */
- $courserenderer = $this->page->get_renderer('core', 'course');
$ismoving = ismoving($course->id);
course_create_sections_if_missing($course, 0);
$modinfo = get_fast_modinfo($course);
$strmovehere = get_string('movehere');
$strmovefull = strip_tags(get_string('movefull', '', "'$USER->activitycopyname'"));
$strcancel= get_string('cancel');
- $stractivityclipboard = $USER->activitycopyname;
} else {
$strmove = get_string('move');
}
- $editbuttons = '';
if ($ismoving) {
$this->content->icons[] = '<img src="'.$OUTPUT->pix_url('t/move') . '" class="iconsmall" alt="" />';
}
if (!empty($modinfo->sections[0])) {
- $options = array('overflowdiv'=>true);
foreach ($modinfo->sections[0] as $modnumber) {
$mod = $modinfo->cms[$modnumber];
if (!$mod->uservisible) {
} else {
$indent = '';
}
- $url = $mod->url;
- if (!$url) {
+ if (!$mod->url) {
$content = $mod->get_formatted_content(array('overflowdiv' => true, 'noclean' => true));
} else {
- //Accessibility: incidental image - should be empty Alt text
- $attrs = array();
- $attrs['title'] = $mod->modfullname;
- $attrs['class'] = $mod->extraclasses . ' activity-action';
- if ($mod->onclick) {
- // Get on-click attribute value if specified and decode the onclick - it
- // has already been encoded for display.
- $attrs['onclick'] = htmlspecialchars_decode($mod->onclick);
- }
- if (!$mod->visible) {
- $attrs['class'] .= ' dimmed';
- }
-
- $icon = '<img src="' . $mod->get_icon_url() . '" class="icon" alt="" />';
- $content = html_writer::link($url, $icon . $mod->get_formatted_name(), $attrs);
+ $content = html_writer::div($courserenderer->course_section_cm_name($mod), ' activity');
}
- $this->content->items[] = $indent.html_writer::div($content . $editbuttons, 'main-menu-content');
+ $this->content->items[] = $indent. $content . $editbuttons;
}
}
}
.block_site_main_menu .footer { margin-top: 1em; }
.block_site_main_menu .section_add_menus noscript div { display: inline;}
.block_site_main_menu .mod-indent,
-.block_site_main_menu .main-menu-content { display: table-cell; }
-.block_site_main_menu .main-menu-content > .activity-action { display: block; }
+.block_site_main_menu .activity { display: table-cell; }
--- /dev/null
+@block @block_main_menu
+Feature: Edit activities in main menu block
+ In order to use main menu block
+ As an admin
+ I need to add and edit activities there
+
+ @javascript
+ Scenario: Edit name of acitivity in-place in site main menu block
+ Given I log in as "admin"
+ And I am on site homepage
+ And I navigate to "Turn editing on" node in "Front page settings"
+ When I add a "Forum" to section "0" and I fill the form with:
+ | Forum name | My forum name |
+ And I click on "Edit title" "link" in the "//.[contains(@class,'block_site_main_menu')]//li[contains(.,'My forum name')]" "xpath_element"
+ And I set the field "New name for activity My forum name" to "New forum name"
+ And I press key "13" in the field "New name for activity My forum name"
+ Then I should not see "My forum name"
+ And I should see "New forum name"
+ And I follow "New forum name"
+ And I should not see "My forum name"
+ And I should see "New forum name"
}
$course = $this->page->course;
+ $courserenderer = $this->page->get_renderer('core', 'course');
require_once($CFG->dirroot.'/course/lib.php');
/// extra fast view mode
if (!$isediting) {
if (!empty($modinfo->sections[0])) {
- $options = array('overflowdiv'=>true);
foreach($modinfo->sections[0] as $cmid) {
$cm = $modinfo->cms[$cmid];
if (!$cm->uservisible) {
continue;
}
- $content = $cm->get_formatted_content(array('overflowdiv' => true, 'noclean' => true));
- $instancename = $cm->get_formatted_name();
-
- if (!($url = $cm->url)) {
+ if (!$cm->url) {
+ $content = $cm->get_formatted_content(array('overflowdiv' => true, 'noclean' => true));
$this->content->items[] = $content;
$this->content->icons[] = '';
} else {
- $linkcss = $cm->visible ? '' : ' class="dimmed" ';
- //Accessibility: incidental image - should be empty Alt text
- $icon = '<img src="' . $cm->get_icon_url() . '" class="icon" alt="" /> ';
- $this->content->items[] = '<a title="'.$cm->modplural.'" '.$linkcss.' '.$cm->extra.
- ' href="' . $url . '">' . $icon . $instancename . '</a>';
+ $this->content->items[] = html_writer::div($courserenderer->course_section_cm_name($cm), 'activity');
}
}
}
// Slow & hacky editing mode.
- /** @var core_course_renderer $courserenderer */
- $courserenderer = $this->page->get_renderer('core', 'course');
$ismoving = ismoving($course->id);
- $modinfo = get_fast_modinfo($course);
$section = $modinfo->get_section_info(0);
if ($ismoving) {
$strmovehere = get_string('movehere');
$strmovefull = strip_tags(get_string('movefull', '', "'$USER->activitycopyname'"));
$strcancel= get_string('cancel');
- $stractivityclipboard = $USER->activitycopyname;
} else {
$strmove = get_string('move');
}
- $editbuttons = '';
if ($ismoving) {
$this->content->icons[] = ' <img align="bottom" src="'.$OUTPUT->pix_url('t/move') . '" class="iconsmall" alt="" />';
}
if (!empty($modinfo->sections[0])) {
- $options = array('overflowdiv'=>true);
foreach ($modinfo->sections[0] as $modnumber) {
$mod = $modinfo->cms[$modnumber];
if (!$mod->uservisible) {
'<img style="height:16px; width:80px; border:0px" src="'.$OUTPUT->pix_url('movehere') . '" alt="'.$strmovehere.'" /></a>';
$this->content->icons[] = '';
}
- $content = $mod->get_formatted_content(array('overflowdiv' => true, 'noclean' => true));
- $instancename = $mod->get_formatted_name();
-
- $linkcss = $mod->visible ? '' : ' class="dimmed" ';
-
- if (!($url = $mod->url)) {
+ if (!$mod->url) {
+ $content = $mod->get_formatted_content(array('overflowdiv' => true, 'noclean' => true));
$this->content->items[] = $content . $editbuttons;
$this->content->icons[] = '';
} else {
- //Accessibility: incidental image - should be empty Alt text
- $icon = '<img src="' . $mod->get_icon_url() . '" class="icon" alt="" /> ';
- $this->content->items[] = '<a title="' . $mod->modfullname . '" ' . $linkcss . ' ' . $mod->extra .
- ' href="' . $url . '">' . $icon . $instancename . '</a>' . $editbuttons;
+ $this->content->items[] = html_writer::div($courserenderer->course_section_cm_name($mod), 'activity') .
+ $editbuttons;
}
}
}
--- /dev/null
+@block @block_social_activities
+Feature: Edit activities in social activities block
+ In order to use social activities block
+ As a teacher
+ I need to add and edit activities there
+
+ @javascript
+ Scenario: Edit name of acitivity in-place in social activities block
+ Given the following "courses" exist:
+ | fullname | shortname | format |
+ | Course 1 | C1 | social |
+ And the following "users" exist:
+ | username | firstname | lastname |
+ | user1 | User | One |
+ And the following "course enrolments" exist:
+ | user | course | role |
+ | user1 | C1 | editingteacher |
+ Given I log in as "user1"
+ And I follow "Course 1"
+ And I turn editing mode on
+ And I set the field "Add an activity to section 'section 0'" to "Forum"
+ And I set the field "Forum name" to "My forum name"
+ And I press "Save and return to course"
+ And I click on "Edit title" "link" in the "//.[contains(@class,'block_social_activities')]//li[contains(.,'My forum name')]" "xpath_element"
+ And I set the field "New name for activity My forum name" to "New forum name"
+ And I press key "13" in the field "New name for activity My forum name"
+ Then I should not see "My forum name" in the "Social activities" "block"
+ And I should see "New forum name"
+ And I follow "New forum name"
+ And I should not see "My forum name"
+ And I should see "New forum name"
require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
-use Behat\Behat\Context\Step\Given as Given;
+use Moodle\BehatExtension\Context\Step\Given as Given;
/**
* Blocks management steps definitions.
// NOTE: no MOODLE_INTERNAL used, this file may be required by behat before including /config.php.
require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
-use Behat\Behat\Context\Step\Given as Given;
+use Moodle\BehatExtension\Context\Step\Given as Given;
use Behat\Gherkin\Node\TableNode as TableNode;
/**
--- /dev/null
+<?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/>.
+
+/**
+ * Contains class core_cohort\output\cohortidnumber
+ *
+ * @package core_cohort
+ * @copyright 2016 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_cohort\output;
+
+use lang_string;
+
+/**
+ * Class to prepare a cohort idnumber for display.
+ *
+ * @package core_cohort
+ * @copyright 2016 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class cohortidnumber extends \core\output\inplace_editable {
+ /**
+ * Constructor.
+ *
+ * @param stdClass $cohort
+ */
+ public function __construct($cohort) {
+ $cohortcontext = \context::instance_by_id($cohort->contextid);
+ $editable = has_capability('moodle/cohort:manage', $cohortcontext);
+ $displayvalue = s($cohort->idnumber); // All idnumbers are plain text.
+ parent::__construct('core_cohort', 'cohortidnumber', $cohort->id, $editable,
+ $displayvalue,
+ $cohort->idnumber,
+ new lang_string('editcohortidnumber', 'cohort'),
+ new lang_string('newidnumberfor', 'cohort', $displayvalue));
+ }
+
+ /**
+ * Updates cohort name and returns instance of this object
+ *
+ * @param int $cohortid
+ * @param string $newvalue
+ * @return static
+ */
+ public static function update($cohortid, $newvalue) {
+ global $DB;
+ $cohort = $DB->get_record('cohort', array('id' => $cohortid), '*', MUST_EXIST);
+ $cohortcontext = \context::instance_by_id($cohort->contextid);
+ require_capability('moodle/cohort:manage', $cohortcontext);
+ $record = (object)array('id' => $cohort->id, 'idnumber' => $newvalue, 'contextid' => $cohort->contextid);
+ cohort_update_cohort($record);
+ $cohort->idnumber = $newvalue;
+ return new static($cohort);
+ }
+}
--- /dev/null
+<?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/>.
+
+/**
+ * Contains class core_cohort\output\cohortname
+ *
+ * @package core_cohort
+ * @copyright 2016 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_cohort\output;
+
+use lang_string;
+
+/**
+ * Class to prepare a cohort name for display.
+ *
+ * @package core_cohort
+ * @copyright 2016 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class cohortname extends \core\output\inplace_editable {
+ /**
+ * Constructor.
+ *
+ * @param stdClass $cohort
+ */
+ public function __construct($cohort) {
+ $cohortcontext = \context::instance_by_id($cohort->contextid);
+ $editable = has_capability('moodle/cohort:manage', $cohortcontext);
+ $displayvalue = format_string($cohort->name, true, array('context' => $cohortcontext));
+ parent::__construct('core_cohort', 'cohortname', $cohort->id, $editable,
+ $displayvalue,
+ $cohort->name,
+ new lang_string('editcohortname', 'cohort'),
+ new lang_string('newnamefor', 'cohort', $displayvalue));
+ }
+
+ /**
+ * Updates cohort name and returns instance of this object
+ *
+ * @param int $cohortid
+ * @param string $newvalue
+ * @return static
+ */
+ public static function update($cohortid, $newvalue) {
+ global $DB;
+ $cohort = $DB->get_record('cohort', array('id' => $cohortid), '*', MUST_EXIST);
+ $cohortcontext = \context::instance_by_id($cohort->contextid);
+ require_capability('moodle/cohort:manage', $cohortcontext);
+ $newvalue = clean_param($newvalue, PARAM_TEXT);
+ if (strval($newvalue) !== '') {
+ $record = (object)array('id' => $cohort->id, 'name' => $newvalue, 'contextid' => $cohort->contextid);
+ cohort_update_cohort($record);
+ $cohort->name = $newvalue;
+ }
+ return new static($cohort);
+ }
+}
$line[] = $cohortcontext->get_context_name(false);
}
}
- $line[] = format_string($cohort->name);
- $line[] = s($cohort->idnumber); // All idnumbers are plain text.
+ $tmpl = new \core_cohort\output\cohortname($cohort);
+ $line[] = $OUTPUT->render_from_template('core/inplace_editable', $tmpl->export_for_template($OUTPUT));
+ $tmpl = new \core_cohort\output\cohortidnumber($cohort);
+ $line[] = $OUTPUT->render_from_template('core/inplace_editable', $tmpl->export_for_template($OUTPUT));
$line[] = format_text($cohort->description, $cohort->descriptionformat);
$line[] = $DB->count_records('cohort_members', array('cohortid'=>$cohort->id));
}
return null;
}
+
+/**
+ * Implements callback inplace_editable() allowing to edit values in-place
+ *
+ * @param string $itemtype
+ * @param int $itemid
+ * @param mixed $newvalue
+ * @return \core\output\inplace_editable
+ */
+function core_cohort_inplace_editable($itemtype, $itemid, $newvalue) {
+ if ($itemtype === 'cohortname') {
+ return \core_cohort\output\cohortname::update($itemid, $newvalue);
+ } else if ($itemtype === 'cohortidnumber') {
+ return \core_cohort\output\cohortidnumber::update($itemid, $newvalue);
+ }
+}
And the "Current users" select box should contain "Third User (third@example.com)"
And the "Current users" select box should contain "Forth User (forth@example.com)"
And the "Current users" select box should not contain "First User (first@example.com)"
+
+ @javascript
+ Scenario: Edit cohort name in-place
+ When I follow "Cohorts"
+ And I click on "Edit cohort name" "link" in the "Test cohort name" "table_row"
+ And I set the field "New name for cohort Test cohort name" to "Students cohort"
+ And I press key "13" in the field "New name for cohort Test cohort name"
+ Then I should not see "Test cohort name"
+ And I should see "Students cohort"
+ And I follow "Cohorts"
+ And I should see "Students cohort"
require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
-use Behat\Behat\Context\Step\Given as Given;
+use Moodle\BehatExtension\Context\Step\Given as Given;
/**
* Steps definitions for cohort actions.
require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
-use Behat\Behat\Context\Step\Given,
- Behat\Behat\Context\Step\Then,
+use Moodle\BehatExtension\Context\Step\Given,
+ Moodle\BehatExtension\Context\Step\Then,
Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException;
/**
"require-dev": {
"phpunit/phpunit": "4.8.*",
"phpunit/dbUnit": "1.4.*",
- "moodlehq/behat-extension": "1.31.0"
+ "moodlehq/behat-extension": "3.31.0"
}
}
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
- "hash": "a957f7332dd1d221be96fb356cb9f03d",
- "content-hash": "463a75022982e6a64bd9fc0513d9b44c",
+ "hash": "769fa23c4b31f60c9fb82d5b23171e0f",
+ "content-hash": "5fca4c69d043cb1f985fc08cd82a64f8",
"packages": [],
"packages-dev": [
{
"name": "behat/behat",
- "version": "v2.5.5",
+ "version": "v3.0.15",
"source": {
"type": "git",
"url": "https://github.com/Behat/Behat.git",
- "reference": "c1e48826b84669c97a1efa78459aedfdcdcf2120"
+ "reference": "b35ae3d45332d80c532af69cc36f780a9397a996"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Behat/Behat/zipball/c1e48826b84669c97a1efa78459aedfdcdcf2120",
- "reference": "c1e48826b84669c97a1efa78459aedfdcdcf2120",
+ "url": "https://api.github.com/repos/Behat/Behat/zipball/b35ae3d45332d80c532af69cc36f780a9397a996",
+ "reference": "b35ae3d45332d80c532af69cc36f780a9397a996",
"shasum": ""
},
"require": {
- "behat/gherkin": "~2.3.0",
- "php": ">=5.3.1",
+ "behat/gherkin": "~4.3",
+ "behat/transliterator": "~1.0",
+ "ext-mbstring": "*",
+ "php": ">=5.3.3",
+ "symfony/class-loader": "~2.1",
"symfony/config": "~2.3",
- "symfony/console": "~2.0",
- "symfony/dependency-injection": "~2.0",
- "symfony/event-dispatcher": "~2.0",
- "symfony/finder": "~2.0",
+ "symfony/console": "~2.1",
+ "symfony/dependency-injection": "~2.1",
+ "symfony/event-dispatcher": "~2.1",
"symfony/translation": "~2.3",
- "symfony/yaml": "~2.0"
+ "symfony/yaml": "~2.1"
},
"require-dev": {
- "phpunit/phpunit": "~3.7.19"
+ "phpspec/prophecy-phpunit": "~1.0",
+ "phpunit/phpunit": "~4.0",
+ "symfony/process": "~2.1"
},
"suggest": {
"behat/mink-extension": "for integration with Mink testing framework",
"bin/behat"
],
"type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0.x-dev"
+ }
+ },
"autoload": {
"psr-0": {
- "Behat\\Behat": "src/"
+ "Behat\\Behat": "src/",
+ "Behat\\Testwork": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"description": "Scenario-oriented BDD framework for PHP 5.3",
"homepage": "http://behat.org/",
"keywords": [
+ "Agile",
"BDD",
- "Behat",
- "Symfony2"
+ "ScenarioBDD",
+ "Scrum",
+ "StoryBDD",
+ "User story",
+ "business",
+ "development",
+ "documentation",
+ "examples",
+ "symfony",
+ "testing"
],
- "time": "2015-06-01 09:37:55"
+ "time": "2015-02-22 14:10:33"
},
{
"name": "behat/gherkin",
- "version": "v2.3.5",
+ "version": "v4.4.1",
"source": {
"type": "git",
"url": "https://github.com/Behat/Gherkin.git",
- "reference": "2b33963da5525400573560c173ab5c9c057e1852"
+ "reference": "1576b485c0f92ef6d27da9c4bbfc57ee30cf6911"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Behat/Gherkin/zipball/2b33963da5525400573560c173ab5c9c057e1852",
- "reference": "2b33963da5525400573560c173ab5c9c057e1852",
+ "url": "https://api.github.com/repos/Behat/Gherkin/zipball/1576b485c0f92ef6d27da9c4bbfc57ee30cf6911",
+ "reference": "1576b485c0f92ef6d27da9c4bbfc57ee30cf6911",
"shasum": ""
},
"require": {
- "php": ">=5.3.1",
- "symfony/finder": "~2.0"
+ "php": ">=5.3.1"
},
"require-dev": {
- "symfony/config": "~2.0",
- "symfony/translation": "~2.0",
- "symfony/yaml": "~2.0"
+ "phpunit/phpunit": "~4.0",
+ "symfony/yaml": "~2.1"
},
"suggest": {
- "symfony/config": "If you want to use Config component to manage resources",
- "symfony/translation": "If you want to use Symfony2 translations adapter",
"symfony/yaml": "If you want to parse features, represented in YAML files"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-develop": "2.2-dev"
+ "dev-master": "4.4-dev"
}
},
"autoload": {
"keywords": [
"BDD",
"Behat",
+ "Cucumber",
"DSL",
- "Symfony2",
+ "gherkin",
"parser"
],
- "time": "2013-10-15 11:22:17"
+ "time": "2015-12-30 14:47:00"
},
{
"name": "behat/mink",
- "version": "v1.5.0",
+ "version": "v1.7.0",
"source": {
"type": "git",
"url": "https://github.com/minkphp/Mink.git",
- "reference": "0769e6d9726c140a54dbf827a438c0f9912749fe"
+ "reference": "6c129030ec2cc029905cf969a56ca8f087b2dfdf"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/minkphp/Mink/zipball/0769e6d9726c140a54dbf827a438c0f9912749fe",
- "reference": "0769e6d9726c140a54dbf827a438c0f9912749fe",
+ "url": "https://api.github.com/repos/minkphp/Mink/zipball/6c129030ec2cc029905cf969a56ca8f087b2dfdf",
+ "reference": "6c129030ec2cc029905cf969a56ca8f087b2dfdf",
"shasum": ""
},
"require": {
"php": ">=5.3.1",
- "symfony/css-selector": "~2.0"
+ "symfony/css-selector": "~2.1"
+ },
+ "require-dev": {
+ "symfony/phpunit-bridge": "~2.7"
},
"suggest": {
"behat/mink-browserkit-driver": "extremely fast headless driver for Symfony\\Kernel-based apps (Sf2, Silex)",
"type": "library",
"extra": {
"branch-alias": {
- "dev-develop": "1.5.x-dev"
+ "dev-master": "1.7.x-dev"
}
},
"autoload": {
- "psr-0": {
- "Behat\\Mink": "src/"
+ "psr-4": {
+ "Behat\\Mink\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"homepage": "http://everzet.com"
}
],
- "description": "Web acceptance testing framework for PHP 5.3",
+ "description": "Browser controller/emulator abstraction for PHP",
"homepage": "http://mink.behat.org/",
"keywords": [
"browser",
"testing",
"web"
],
- "time": "2013-04-13 23:39:27"
+ "time": "2015-09-20 20:24:03"
},
{
"name": "behat/mink-browserkit-driver",
- "version": "v1.1.0",
+ "version": "v1.3.1",
"source": {
"type": "git",
"url": "https://github.com/minkphp/MinkBrowserKitDriver.git",
- "reference": "63960c8fcad4529faad1ff33e950217980baa64c"
+ "reference": "2650f5420e713e3807c7f09a07370a4f48367bf9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/minkphp/MinkBrowserKitDriver/zipball/63960c8fcad4529faad1ff33e950217980baa64c",
- "reference": "63960c8fcad4529faad1ff33e950217980baa64c",
+ "url": "https://api.github.com/repos/minkphp/MinkBrowserKitDriver/zipball/2650f5420e713e3807c7f09a07370a4f48367bf9",
+ "reference": "2650f5420e713e3807c7f09a07370a4f48367bf9",
"shasum": ""
},
"require": {
- "behat/mink": "~1.5.0",
- "php": ">=5.3.1",
- "symfony/browser-kit": "~2.0",
- "symfony/dom-crawler": "~2.0"
+ "behat/mink": "~1.7@dev",
+ "php": ">=5.3.6",
+ "symfony/browser-kit": "~2.3|~3.0",
+ "symfony/dom-crawler": "~2.3|~3.0"
},
"require-dev": {
- "silex/silex": "@dev"
+ "silex/silex": "~1.2",
+ "symfony/phpunit-bridge": "~2.7|~3.0"
},
"type": "mink-driver",
"extra": {
"branch-alias": {
- "dev-master": "1.1.x-dev"
+ "dev-master": "1.3.x-dev"
}
},
"autoload": {
- "psr-0": {
- "Behat\\Mink\\Driver": "src/"
+ "psr-4": {
+ "Behat\\Mink\\Driver\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"browser",
"testing"
],
- "time": "2013-04-13 23:46:30"
+ "time": "2016-01-19 16:59:07"
},
{
"name": "behat/mink-extension",
- "version": "v1.3.3",
+ "version": "v2.2",
"source": {
"type": "git",
"url": "https://github.com/Behat/MinkExtension.git",
- "reference": "b885b9407cba50a954f72c69ed1b2f8d3bc694f8"
+ "reference": "5b4bda64ff456104564317e212c823e45cad9d59"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Behat/MinkExtension/zipball/b885b9407cba50a954f72c69ed1b2f8d3bc694f8",
- "reference": "b885b9407cba50a954f72c69ed1b2f8d3bc694f8",
+ "url": "https://api.github.com/repos/Behat/MinkExtension/zipball/5b4bda64ff456104564317e212c823e45cad9d59",
+ "reference": "5b4bda64ff456104564317e212c823e45cad9d59",
"shasum": ""
},
"require": {
- "behat/behat": "~2.5.0",
+ "behat/behat": "~3.0,>=3.0.5",
"behat/mink": "~1.5",
"php": ">=5.3.2",
- "symfony/config": "~2.2"
+ "symfony/config": "~2.2|~3.0"
},
"require-dev": {
- "behat/mink-goutte-driver": "~1.0",
- "fabpot/goutte": "~1.0"
+ "behat/mink-goutte-driver": "~1.1",
+ "phpspec/phpspec": "~2.0"
},
"type": "behat-extension",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.1.x-dev"
+ }
+ },
"autoload": {
"psr-0": {
"Behat\\MinkExtension": "src/"
"MIT"
],
"authors": [
+ {
+ "name": "Christophe Coevoet",
+ "email": "stof@notk.org"
+ },
{
"name": "Konstantin Kudryashov",
- "email": "ever.zet@gmail.com",
- "homepage": "http://everzet.com"
+ "email": "ever.zet@gmail.com"
}
],
"description": "Mink extension for Behat",
- "homepage": "http://mink.behat.org",
+ "homepage": "http://extensions.behat.org/mink",
"keywords": [
"browser",
"gui",
"test",
"web"
],
- "time": "2014-05-15 19:27:39"
+ "time": "2016-02-15 07:55:18"
},
{
"name": "behat/mink-goutte-driver",
- "version": "v1.0.9",
+ "version": "v1.2.0",
"source": {
"type": "git",
"url": "https://github.com/minkphp/MinkGoutteDriver.git",
- "reference": "fa1b073b48761464feb0b05e6825da44b20118d8"
+ "reference": "c8e254f127d6f2242b994afd4339fb62d471df3f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/minkphp/MinkGoutteDriver/zipball/fa1b073b48761464feb0b05e6825da44b20118d8",
- "reference": "fa1b073b48761464feb0b05e6825da44b20118d8",
+ "url": "https://api.github.com/repos/minkphp/MinkGoutteDriver/zipball/c8e254f127d6f2242b994afd4339fb62d471df3f",
+ "reference": "c8e254f127d6f2242b994afd4339fb62d471df3f",
"shasum": ""
},
"require": {
- "behat/mink-browserkit-driver": ">=1.0.5,<1.2.0",
- "fabpot/goutte": "~1.0.1",
+ "behat/mink": "~1.6@dev",
+ "behat/mink-browserkit-driver": "~1.2@dev",
+ "fabpot/goutte": "~1.0.4|~2.0|~3.1",
"php": ">=5.3.1"
},
+ "require-dev": {
+ "symfony/phpunit-bridge": "~2.7"
+ },
"type": "mink-driver",
"extra": {
"branch-alias": {
- "dev-master": "1.0.x-dev"
+ "dev-master": "1.2.x-dev"
}
},
"autoload": {
- "psr-0": {
- "Behat\\Mink\\Driver": "src/"
+ "psr-4": {
+ "Behat\\Mink\\Driver\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"headless",
"testing"
],
- "time": "2013-07-03 18:43:54"
+ "time": "2015-09-21 21:31:11"
},
{
"name": "behat/mink-selenium2-driver",
- "version": "v1.1.1",
+ "version": "v1.3.0",
"source": {
"type": "git",
"url": "https://github.com/minkphp/MinkSelenium2Driver.git",
- "reference": "bcf1b537de37db6db0822d9e7bd97e600fd7a476"
+ "reference": "bedbf1999c7ba1bc6921b30ee2eadf383e7ff5c9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/minkphp/MinkSelenium2Driver/zipball/bcf1b537de37db6db0822d9e7bd97e600fd7a476",
- "reference": "bcf1b537de37db6db0822d9e7bd97e600fd7a476",
+ "url": "https://api.github.com/repos/minkphp/MinkSelenium2Driver/zipball/bedbf1999c7ba1bc6921b30ee2eadf383e7ff5c9",
+ "reference": "bedbf1999c7ba1bc6921b30ee2eadf383e7ff5c9",
"shasum": ""
},
"require": {
- "behat/mink": "~1.5.0",
- "instaclick/php-webdriver": "~1.0.12",
+ "behat/mink": "~1.7@dev",
+ "instaclick/php-webdriver": "~1.1",
"php": ">=5.3.1"
},
+ "require-dev": {
+ "symfony/phpunit-bridge": "~2.7"
+ },
"type": "mink-driver",
"extra": {
"branch-alias": {
- "dev-master": "1.1.x-dev"
+ "dev-master": "1.3.x-dev"
}
},
"autoload": {
- "psr-0": {
- "Behat\\Mink\\Driver": "src/"
+ "psr-4": {
+ "Behat\\Mink\\Driver\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"testing",
"webdriver"
],
- "time": "2013-06-02 19:09:45"
+ "time": "2015-09-21 21:02:54"
+ },
+ {
+ "name": "behat/transliterator",
+ "version": "v1.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Behat/Transliterator.git",
+ "reference": "868e05be3a9f25ba6424c2dd4849567f50715003"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Behat/Transliterator/zipball/868e05be3a9f25ba6424c2dd4849567f50715003",
+ "reference": "868e05be3a9f25ba6424c2dd4849567f50715003",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "Behat\\Transliterator": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "Artistic-1.0"
+ ],
+ "description": "String transliterator",
+ "keywords": [
+ "i18n",
+ "slug",
+ "transliterator"
+ ],
+ "time": "2015-09-28 16:26:35"
},
{
"name": "doctrine/instantiator",
},
{
"name": "fabpot/goutte",
- "version": "v1.0.7",
+ "version": "v2.0.4",
"source": {
"type": "git",
"url": "https://github.com/FriendsOfPHP/Goutte.git",
- "reference": "794b196e76bdd37b5155cdecbad311f0a3b07625"
+ "reference": "0ad3ee6dc2d0aaa832a80041a1e09bf394e99802"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/FriendsOfPHP/Goutte/zipball/794b196e76bdd37b5155cdecbad311f0a3b07625",
- "reference": "794b196e76bdd37b5155cdecbad311f0a3b07625",
+ "url": "https://api.github.com/repos/FriendsOfPHP/Goutte/zipball/0ad3ee6dc2d0aaa832a80041a1e09bf394e99802",
+ "reference": "0ad3ee6dc2d0aaa832a80041a1e09bf394e99802",
"shasum": ""
},
"require": {
- "ext-curl": "*",
- "guzzle/http": "~3.1",
- "php": ">=5.3.0",
+ "guzzlehttp/guzzle": ">=4,<6",
+ "php": ">=5.4.0",
"symfony/browser-kit": "~2.1",
"symfony/css-selector": "~2.1",
- "symfony/dom-crawler": "~2.1",
- "symfony/finder": "~2.1",
- "symfony/process": "~2.1"
- },
- "require-dev": {
- "guzzle/plugin-history": "~3.1",
- "guzzle/plugin-mock": "~3.1"
+ "symfony/dom-crawler": "~2.1"
},
"type": "application",
"extra": {
"branch-alias": {
- "dev-master": "1.0-dev"
+ "dev-master": "2.0-dev"
}
},
"autoload": {
- "psr-0": {
- "Goutte": "."
+ "psr-4": {
+ "Goutte\\": "Goutte"
}
},
"notification-url": "https://packagist.org/downloads/",
}
],
"description": "A simple PHP Web Scraper",
- "homepage": "https://github.com/fabpot/Goutte",
+ "homepage": "https://github.com/FriendsOfPHP/Goutte",
"keywords": [
"scraper"
],
- "time": "2014-10-09 15:52:51"
+ "time": "2015-05-05 21:14:57"
},
{
"name": "guzzlehttp/guzzle",
- "version": "v3.8.1",
+ "version": "5.3.0",
"source": {
"type": "git",
"url": "https://github.com/guzzle/guzzle.git",
- "reference": "4de0618a01b34aa1c8c33a3f13f396dcd3882eba"
+ "reference": "f3c8c22471cb55475105c14769644a49c3262b93"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/guzzle/guzzle/zipball/4de0618a01b34aa1c8c33a3f13f396dcd3882eba",
- "reference": "4de0618a01b34aa1c8c33a3f13f396dcd3882eba",
+ "url": "https://api.github.com/repos/guzzle/guzzle/zipball/f3c8c22471cb55475105c14769644a49c3262b93",
+ "reference": "f3c8c22471cb55475105c14769644a49c3262b93",
"shasum": ""
},
"require": {
- "ext-curl": "*",
- "php": ">=5.3.3",
- "symfony/event-dispatcher": ">=2.1"
- },
- "replace": {
- "guzzle/batch": "self.version",
- "guzzle/cache": "self.version",
- "guzzle/common": "self.version",
- "guzzle/http": "self.version",
- "guzzle/inflection": "self.version",
- "guzzle/iterator": "self.version",
- "guzzle/log": "self.version",
- "guzzle/parser": "self.version",
- "guzzle/plugin": "self.version",
- "guzzle/plugin-async": "self.version",
- "guzzle/plugin-backoff": "self.version",
- "guzzle/plugin-cache": "self.version",
- "guzzle/plugin-cookie": "self.version",
- "guzzle/plugin-curlauth": "self.version",
- "guzzle/plugin-error-response": "self.version",
- "guzzle/plugin-history": "self.version",
- "guzzle/plugin-log": "self.version",
- "guzzle/plugin-md5": "self.version",
- "guzzle/plugin-mock": "self.version",
- "guzzle/plugin-oauth": "self.version",
- "guzzle/service": "self.version",
- "guzzle/stream": "self.version"
+ "guzzlehttp/ringphp": "^1.1",
+ "php": ">=5.4.0"
},
"require-dev": {
- "doctrine/cache": "*",
- "monolog/monolog": "1.*",
- "phpunit/phpunit": "3.7.*",
- "psr/log": "1.0.*",
- "symfony/class-loader": "*",
- "zendframework/zend-cache": "<2.3",
- "zendframework/zend-log": "<2.3"
+ "ext-curl": "*",
+ "phpunit/phpunit": "^4.0",
+ "psr/log": "^1.0"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.8-dev"
+ "dev-master": "5.0-dev"
}
},
"autoload": {
- "psr-0": {
- "Guzzle": "src/",
- "Guzzle\\Tests": "tests/"
+ "psr-4": {
+ "GuzzleHttp\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
- },
- {
- "name": "Guzzle Community",
- "homepage": "https://github.com/guzzle/guzzle/contributors"
}
],
"description": "Guzzle is a PHP HTTP client library and framework for building RESTful web service clients",
"rest",
"web service"
],
- "time": "2014-01-28 22:29:15"
+ "time": "2015-05-20 03:47:55"
+ },
+ {
+ "name": "guzzlehttp/ringphp",
+ "version": "1.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/RingPHP.git",
+ "reference": "dbbb91d7f6c191e5e405e900e3102ac7f261bc0b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/RingPHP/zipball/dbbb91d7f6c191e5e405e900e3102ac7f261bc0b",
+ "reference": "dbbb91d7f6c191e5e405e900e3102ac7f261bc0b",
+ "shasum": ""
+ },
+ "require": {
+ "guzzlehttp/streams": "~3.0",
+ "php": ">=5.4.0",
+ "react/promise": "~2.0"
+ },
+ "require-dev": {
+ "ext-curl": "*",
+ "phpunit/phpunit": "~4.0"
+ },
+ "suggest": {
+ "ext-curl": "Guzzle will use specific adapters if cURL is present"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\Ring\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "description": "Provides a simple API and specification that abstracts away the details of HTTP into a single PHP function.",
+ "time": "2015-05-20 03:37:09"
+ },
+ {
+ "name": "guzzlehttp/streams",
+ "version": "3.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/streams.git",
+ "reference": "47aaa48e27dae43d39fc1cea0ccf0d84ac1a2ba5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/streams/zipball/47aaa48e27dae43d39fc1cea0ccf0d84ac1a2ba5",
+ "reference": "47aaa48e27dae43d39fc1cea0ccf0d84ac1a2ba5",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.4.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\Stream\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "description": "Provides a simple abstraction over streams of data",
+ "homepage": "http://guzzlephp.org/",
+ "keywords": [
+ "Guzzle",
+ "stream"
+ ],
+ "time": "2014-10-12 19:18:40"
},
{
"name": "instaclick/php-webdriver",
- "version": "1.0.17",
+ "version": "1.4.3",
"source": {
"type": "git",
"url": "https://github.com/instaclick/php-webdriver.git",
- "reference": "47a6019553a7a5b42d35493276ffc2c9252c53d5"
+ "reference": "0c20707dcf30a32728fd6bdeeab996c887fdb2fb"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/instaclick/php-webdriver/zipball/47a6019553a7a5b42d35493276ffc2c9252c53d5",
- "reference": "47a6019553a7a5b42d35493276ffc2c9252c53d5",
+ "url": "https://api.github.com/repos/instaclick/php-webdriver/zipball/0c20707dcf30a32728fd6bdeeab996c887fdb2fb",
+ "reference": "0c20707dcf30a32728fd6bdeeab996c887fdb2fb",
"shasum": ""
},
"require": {
"ext-curl": "*",
"php": ">=5.3.2"
},
- "bin": [
- "bin/webunit"
- ],
+ "require-dev": {
+ "satooshi/php-coveralls": "dev-master"
+ },
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.0.x-dev"
+ "dev-master": "1.4.x-dev"
}
},
"autoload": {
{
"name": "Anthon Pang",
"email": "apang@softwaredevelopment.ca",
- "role": "developer"
+ "role": "Fork Maintainer"
}
],
"description": "PHP WebDriver for Selenium 2",
"webdriver",
"webtest"
],
- "time": "2013-10-04 15:03:51"
+ "time": "2015-06-15 20:19:33"
},
{
"name": "moodlehq/behat-extension",
- "version": "v1.31.0",
+ "version": "v3.31.0",
"source": {
"type": "git",
"url": "https://github.com/moodlehq/moodle-behat-extension.git",
- "reference": "b4dd6f7d1ca1c89a44b8b010af8546d7a2808b7b"
+ "reference": "d985e9da29914b0da90d61c47aadc455586eeee5"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/moodlehq/moodle-behat-extension/zipball/b4dd6f7d1ca1c89a44b8b010af8546d7a2808b7b",
- "reference": "b4dd6f7d1ca1c89a44b8b010af8546d7a2808b7b",
+ "url": "https://api.github.com/repos/moodlehq/moodle-behat-extension/zipball/d985e9da29914b0da90d61c47aadc455586eeee5",
+ "reference": "d985e9da29914b0da90d61c47aadc455586eeee5",
"shasum": ""
},
"require": {
- "behat/behat": "2.5.5",
- "behat/mink": "1.5.0",
- "behat/mink-extension": "1.3.3",
- "behat/mink-goutte-driver": "1.0.9",
- "behat/mink-selenium2-driver": "1.1.1",
- "guzzlehttp/guzzle": "~3.1",
+ "behat/behat": "3.0.*",
+ "behat/mink": "~1.7",
+ "behat/mink-extension": "~2.1",
+ "behat/mink-goutte-driver": "~1.2",
+ "behat/mink-selenium2-driver": "~1.3",
"php": ">=5.4.4",
- "symfony/browser-kit": "2.7.5",
- "symfony/css-selector": "2.7.5",
- "symfony/dom-crawler": "2.7.5",
- "symfony/filesystem": "2.7.5",
- "symfony/finder": "2.7.5"
+ "symfony/process": "2.8.*"
},
"type": "library",
"autoload": {
"Behat",
"moodle"
],
- "time": "2016-01-05 02:55:24"
+ "time": "2016-03-04 07:15:40"
},
{
"name": "phpdocumentor/reflection-docblock",
},
{
"name": "phpspec/prophecy",
- "version": "v1.5.0",
+ "version": "v1.6.0",
"source": {
"type": "git",
"url": "https://github.com/phpspec/prophecy.git",
- "reference": "4745ded9307786b730d7a60df5cb5a6c43cf95f7"
+ "reference": "3c91bdf81797d725b14cb62906f9a4ce44235972"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4745ded9307786b730d7a60df5cb5a6c43cf95f7",
- "reference": "4745ded9307786b730d7a60df5cb5a6c43cf95f7",
+ "url": "https://api.github.com/repos/phpspec/prophecy/zipball/3c91bdf81797d725b14cb62906f9a4ce44235972",
+ "reference": "3c91bdf81797d725b14cb62906f9a4ce44235972",
"shasum": ""
},
"require": {
"doctrine/instantiator": "^1.0.2",
+ "php": "^5.3|^7.0",
"phpdocumentor/reflection-docblock": "~2.0",
- "sebastian/comparator": "~1.1"
+ "sebastian/comparator": "~1.1",
+ "sebastian/recursion-context": "~1.0"
},
"require-dev": {
"phpspec/phpspec": "~2.0"
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.4.x-dev"
+ "dev-master": "1.5.x-dev"
}
},
"autoload": {
"spy",
"stub"
],
- "time": "2015-08-13 10:07:40"
+ "time": "2016-02-15 07:46:21"
},
{
"name": "phpunit/dbunit",
},
{
"name": "phpunit/phpunit",
- "version": "4.8.21",
+ "version": "4.8.23",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
- "reference": "ea76b17bced0500a28098626b84eda12dbcf119c"
+ "reference": "6e351261f9cd33daf205a131a1ba61c6d33bd483"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/ea76b17bced0500a28098626b84eda12dbcf119c",
- "reference": "ea76b17bced0500a28098626b84eda12dbcf119c",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/6e351261f9cd33daf205a131a1ba61c6d33bd483",
+ "reference": "6e351261f9cd33daf205a131a1ba61c6d33bd483",
"shasum": ""
},
"require": {
"testing",
"xunit"
],
- "time": "2015-12-12 07:45:58"
+ "time": "2016-02-11 14:56:33"
},
{
"name": "phpunit/phpunit-mock-objects",
],
"time": "2015-10-02 06:51:40"
},
+ {
+ "name": "react/promise",
+ "version": "v2.2.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/reactphp/promise.git",
+ "reference": "3aacad8bf10c7d83e6fa2089d413529888c2bedf"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/reactphp/promise/zipball/3aacad8bf10c7d83e6fa2089d413529888c2bedf",
+ "reference": "3aacad8bf10c7d83e6fa2089d413529888c2bedf",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.4.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "React\\Promise\\": "src/"
+ },
+ "files": [
+ "src/functions_include.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jan Sorgalla",
+ "email": "jsorgalla@gmail.com"
+ }
+ ],
+ "description": "A lightweight implementation of CommonJS Promises/A for PHP",
+ "time": "2016-02-26 19:09:02"
+ },
{
"name": "sebastian/comparator",
"version": "1.2.0",
},
{
"name": "sebastian/environment",
- "version": "1.3.3",
+ "version": "1.3.5",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/environment.git",
- "reference": "6e7133793a8e5a5714a551a8324337374be209df"
+ "reference": "dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/6e7133793a8e5a5714a551a8324337374be209df",
- "reference": "6e7133793a8e5a5714a551a8324337374be209df",
+ "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf",
+ "reference": "dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf",
"shasum": ""
},
"require": {
"environment",
"hhvm"
],
- "time": "2015-12-02 08:37:27"
+ "time": "2016-02-26 18:40:46"
},
{
"name": "sebastian/exporter",
},
{
"name": "symfony/browser-kit",
- "version": "v2.7.5",
+ "version": "v2.8.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/browser-kit.git",
- "reference": "277a2457776d4cc25706fbdd9d1e4ab2dac884e4"
+ "reference": "6b2085020b4e86fcb7ae44c3ab8ddb91774b33d2"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/browser-kit/zipball/277a2457776d4cc25706fbdd9d1e4ab2dac884e4",
- "reference": "277a2457776d4cc25706fbdd9d1e4ab2dac884e4",
+ "url": "https://api.github.com/repos/symfony/browser-kit/zipball/6b2085020b4e86fcb7ae44c3ab8ddb91774b33d2",
+ "reference": "6b2085020b4e86fcb7ae44c3ab8ddb91774b33d2",
"shasum": ""
},
"require": {
"php": ">=5.3.9",
- "symfony/dom-crawler": "~2.0,>=2.0.5"
+ "symfony/dom-crawler": "~2.0,>=2.0.5|~3.0.0"
},
"require-dev": {
- "symfony/css-selector": "~2.0,>=2.0.5",
- "symfony/phpunit-bridge": "~2.7",
- "symfony/process": "~2.0,>=2.0.5"
+ "symfony/css-selector": "~2.0,>=2.0.5|~3.0.0",
+ "symfony/process": "~2.3.34|~2.7,>=2.7.6|~3.0.0"
},
"suggest": {
"symfony/process": ""
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.7-dev"
+ "dev-master": "2.8-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\BrowserKit\\": ""
- }
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
],
"description": "Symfony BrowserKit Component",
"homepage": "https://symfony.com",
- "time": "2015-09-06 08:36:38"
+ "time": "2016-01-27 11:34:40"
+ },
+ {
+ "name": "symfony/class-loader",
+ "version": "v2.8.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/class-loader.git",
+ "reference": "517ab0554b6a5744d04480cb06873ffbd9442d73"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/class-loader/zipball/517ab0554b6a5744d04480cb06873ffbd9442d73",
+ "reference": "517ab0554b6a5744d04480cb06873ffbd9442d73",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.9",
+ "symfony/polyfill-apcu": "~1.1"
+ },
+ "require-dev": {
+ "symfony/finder": "~2.0,>=2.0.5|~3.0.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.8-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\ClassLoader\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony ClassLoader Component",
+ "homepage": "https://symfony.com",
+ "time": "2016-01-30 15:58:35"
},
{
"name": "symfony/config",
- "version": "v2.8.1",
+ "version": "v2.8.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/config.git",
- "reference": "17d4b2e64ce1c6ba7caa040f14469b3c44d7f7d2"
+ "reference": "0f8f94e6a32b5c480024eed5fa5cbd2790d0ad19"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/config/zipball/17d4b2e64ce1c6ba7caa040f14469b3c44d7f7d2",
- "reference": "17d4b2e64ce1c6ba7caa040f14469b3c44d7f7d2",
+ "url": "https://api.github.com/repos/symfony/config/zipball/0f8f94e6a32b5c480024eed5fa5cbd2790d0ad19",
+ "reference": "0f8f94e6a32b5c480024eed5fa5cbd2790d0ad19",
"shasum": ""
},
"require": {
"php": ">=5.3.9",
"symfony/filesystem": "~2.3|~3.0.0"
},
+ "suggest": {
+ "symfony/yaml": "To use the yaml reference dumper"
+ },
"type": "library",
"extra": {
"branch-alias": {
],
"description": "Symfony Config Component",
"homepage": "https://symfony.com",
- "time": "2015-12-26 13:37:56"
+ "time": "2016-02-22 16:12:45"
},
{
"name": "symfony/console",
- "version": "v2.8.1",
+ "version": "v2.8.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
- "reference": "2e06a5ccb19dcf9b89f1c6a677a39a8df773635a"
+ "reference": "56cc5caf051189720b8de974e4746090aaa10d44"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/console/zipball/2e06a5ccb19dcf9b89f1c6a677a39a8df773635a",
- "reference": "2e06a5ccb19dcf9b89f1c6a677a39a8df773635a",
+ "url": "https://api.github.com/repos/symfony/console/zipball/56cc5caf051189720b8de974e4746090aaa10d44",
+ "reference": "56cc5caf051189720b8de974e4746090aaa10d44",
"shasum": ""
},
"require": {
],
"description": "Symfony Console Component",
"homepage": "https://symfony.com",
- "time": "2015-12-22 10:25:57"
+ "time": "2016-02-28 16:20:50"
},
{
"name": "symfony/css-selector",
- "version": "v2.7.5",
+ "version": "v2.8.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/css-selector.git",
- "reference": "abe19cc0429a06be0c133056d1f9859854860970"
+ "reference": "8d83ff9777cdbd83e7f90d9c48f4729823791a5e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/css-selector/zipball/abe19cc0429a06be0c133056d1f9859854860970",
- "reference": "abe19cc0429a06be0c133056d1f9859854860970",
+ "url": "https://api.github.com/repos/symfony/css-selector/zipball/8d83ff9777cdbd83e7f90d9c48f4729823791a5e",
+ "reference": "8d83ff9777cdbd83e7f90d9c48f4729823791a5e",
"shasum": ""
},
"require": {
"php": ">=5.3.9"
},
- "require-dev": {
- "symfony/phpunit-bridge": "~2.7"
- },
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.7-dev"
+ "dev-master": "2.8-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\CssSelector\\": ""
- }
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
],
"description": "Symfony CssSelector Component",
"homepage": "https://symfony.com",
- "time": "2015-09-22 13:49:29"
+ "time": "2016-01-27 05:14:19"
},
{
"name": "symfony/dependency-injection",
- "version": "v2.8.1",
+ "version": "v2.8.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/dependency-injection.git",
- "reference": "c5086d186f538c2711b9af6f727be7b0446979cd"
+ "reference": "62251761a7615435b22ccf562384c588b431be44"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/c5086d186f538c2711b9af6f727be7b0446979cd",
- "reference": "c5086d186f538c2711b9af6f727be7b0446979cd",
+ "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/62251761a7615435b22ccf562384c588b431be44",
+ "reference": "62251761a7615435b22ccf562384c588b431be44",
"shasum": ""
},
"require": {
],
"description": "Symfony DependencyInjection Component",
"homepage": "https://symfony.com",
- "time": "2015-12-26 13:37:56"
+ "time": "2016-02-28 16:34:46"
},
{
"name": "symfony/dom-crawler",
- "version": "v2.7.5",
+ "version": "v2.8.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/dom-crawler.git",
- "reference": "2e185ca136399f902b948694987e62c80099c052"
+ "reference": "e1a4b4c83f5ee6f5902f1d53035e3718909a0c11"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/2e185ca136399f902b948694987e62c80099c052",
- "reference": "2e185ca136399f902b948694987e62c80099c052",
+ "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/e1a4b4c83f5ee6f5902f1d53035e3718909a0c11",
+ "reference": "e1a4b4c83f5ee6f5902f1d53035e3718909a0c11",
"shasum": ""
},
"require": {
- "php": ">=5.3.9"
+ "php": ">=5.3.9",
+ "symfony/polyfill-mbstring": "~1.0"
},
"require-dev": {
- "symfony/css-selector": "~2.3",
- "symfony/phpunit-bridge": "~2.7"
+ "symfony/css-selector": "~2.8|~3.0.0"
},
"suggest": {
"symfony/css-selector": ""
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.7-dev"
+ "dev-master": "2.8-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\DomCrawler\\": ""
- }
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
],
"description": "Symfony DomCrawler Component",
"homepage": "https://symfony.com",
- "time": "2015-09-20 21:13:58"
+ "time": "2016-02-28 16:20:50"
},
{
"name": "symfony/event-dispatcher",
- "version": "v2.8.1",
+ "version": "v2.8.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
- "reference": "a5eb815363c0388e83247e7e9853e5dbc14999cc"
+ "reference": "78c468665c9568c3faaa9c416a7134308f2d85c3"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/a5eb815363c0388e83247e7e9853e5dbc14999cc",
- "reference": "a5eb815363c0388e83247e7e9853e5dbc14999cc",
+ "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/78c468665c9568c3faaa9c416a7134308f2d85c3",
+ "reference": "78c468665c9568c3faaa9c416a7134308f2d85c3",
"shasum": ""
},
"require": {
],
"description": "Symfony EventDispatcher Component",
"homepage": "https://symfony.com",
- "time": "2015-10-30 20:15:42"
+ "time": "2016-01-27 05:14:19"
},
{
"name": "symfony/filesystem",
- "version": "v2.7.5",
+ "version": "v2.8.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
- "reference": "a17f8a17c20e8614c15b8e116e2f4bcde102cfab"
+ "reference": "65cb36b6539b1d446527d60457248f30d045464d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/filesystem/zipball/a17f8a17c20e8614c15b8e116e2f4bcde102cfab",
- "reference": "a17f8a17c20e8614c15b8e116e2f4bcde102cfab",
+ "url": "https://api.github.com/repos/symfony/filesystem/zipball/65cb36b6539b1d446527d60457248f30d045464d",
+ "reference": "65cb36b6539b1d446527d60457248f30d045464d",
"shasum": ""
},
"require": {
"php": ">=5.3.9"
},
- "require-dev": {
- "symfony/phpunit-bridge": "~2.7"
- },
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.7-dev"
+ "dev-master": "2.8-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\Filesystem\\": ""
- }
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
],
"description": "Symfony Filesystem Component",
"homepage": "https://symfony.com",
- "time": "2015-09-09 17:42:36"
+ "time": "2016-02-22 15:02:30"
},
{
- "name": "symfony/finder",
- "version": "v2.7.5",
+ "name": "symfony/polyfill-apcu",
+ "version": "v1.1.0",
"source": {
"type": "git",
- "url": "https://github.com/symfony/finder.git",
- "reference": "8262ab605973afbb3ef74b945daabf086f58366f"
+ "url": "https://github.com/symfony/polyfill-apcu.git",
+ "reference": "d1911e6caeb4b6a4c8e2d5c46b978a66b3745e4c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/finder/zipball/8262ab605973afbb3ef74b945daabf086f58366f",
- "reference": "8262ab605973afbb3ef74b945daabf086f58366f",
+ "url": "https://api.github.com/repos/symfony/polyfill-apcu/zipball/d1911e6caeb4b6a4c8e2d5c46b978a66b3745e4c",
+ "reference": "d1911e6caeb4b6a4c8e2d5c46b978a66b3745e4c",
"shasum": ""
},
"require": {
- "php": ">=5.3.9"
- },
- "require-dev": {
- "symfony/phpunit-bridge": "~2.7"
+ "php": ">=5.3.3"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.7-dev"
+ "dev-master": "1.1-dev"
}
},
"autoload": {
- "psr-4": {
- "Symfony\\Component\\Finder\\": ""
- }
+ "files": [
+ "bootstrap.php"
+ ],
+ "classmap": [
+ "Resources/stubs"
+ ]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
],
"authors": [
{
- "name": "Fabien Potencier",
- "email": "fabien@symfony.com"
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
- "description": "Symfony Finder Component",
+ "description": "Symfony polyfill backporting apcu_* functions to lower PHP versions",
"homepage": "https://symfony.com",
- "time": "2015-09-19 19:59:23"
+ "keywords": [
+ "apcu",
+ "compatibility",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "time": "2016-01-20 09:13:37"
},
{
"name": "symfony/polyfill-mbstring",
- "version": "v1.0.1",
+ "version": "v1.1.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
- "reference": "49ff736bd5d41f45240cec77b44967d76e0c3d25"
+ "reference": "1289d16209491b584839022f29257ad859b8532d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/49ff736bd5d41f45240cec77b44967d76e0c3d25",
- "reference": "49ff736bd5d41f45240cec77b44967d76e0c3d25",
+ "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/1289d16209491b584839022f29257ad859b8532d",
+ "reference": "1289d16209491b584839022f29257ad859b8532d",
"shasum": ""
},
"require": {
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.0-dev"
+ "dev-master": "1.1-dev"
}
},
"autoload": {
"portable",
"shim"
],
- "time": "2015-11-20 09:19:13"
+ "time": "2016-01-20 09:13:37"
},
{
"name": "symfony/process",
- "version": "v2.8.1",
+ "version": "v2.8.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
- "reference": "62c254438b5040bc2217156e1570cf2206e8540c"
+ "reference": "7dedd5b60550f33dca16dd7e94ef8aca8b67bbfe"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/process/zipball/62c254438b5040bc2217156e1570cf2206e8540c",
- "reference": "62c254438b5040bc2217156e1570cf2206e8540c",
+ "url": "https://api.github.com/repos/symfony/process/zipball/7dedd5b60550f33dca16dd7e94ef8aca8b67bbfe",
+ "reference": "7dedd5b60550f33dca16dd7e94ef8aca8b67bbfe",
"shasum": ""
},
"require": {
],
"description": "Symfony Process Component",
"homepage": "https://symfony.com",
- "time": "2015-12-23 11:03:46"
+ "time": "2016-02-02 13:33:15"
},
{
"name": "symfony/translation",
- "version": "v2.8.1",
+ "version": "v2.8.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/translation.git",
- "reference": "c1db87c51251167dd91198b9d1edf897773adb4f"
+ "reference": "b7b4ebadd2b5e614ff7d2d6fc63e0ed0578909c7"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/translation/zipball/c1db87c51251167dd91198b9d1edf897773adb4f",
- "reference": "c1db87c51251167dd91198b9d1edf897773adb4f",
+ "url": "https://api.github.com/repos/symfony/translation/zipball/b7b4ebadd2b5e614ff7d2d6fc63e0ed0578909c7",
+ "reference": "b7b4ebadd2b5e614ff7d2d6fc63e0ed0578909c7",
"shasum": ""
},
"require": {
],
"description": "Symfony Translation Component",
"homepage": "https://symfony.com",
- "time": "2015-12-05 17:37:59"
+ "time": "2016-02-02 09:49:18"
},
{
"name": "symfony/yaml",
- "version": "v2.8.1",
+ "version": "v2.8.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
- "reference": "ac84cbb98b68a6abbc9f5149eb96ccc7b07b8966"
+ "reference": "2a4ee40acb880c56f29fb1b8886e7ffe94f3b995"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/yaml/zipball/ac84cbb98b68a6abbc9f5149eb96ccc7b07b8966",
- "reference": "ac84cbb98b68a6abbc9f5149eb96ccc7b07b8966",
+ "url": "https://api.github.com/repos/symfony/yaml/zipball/2a4ee40acb880c56f29fb1b8886e7ffe94f3b995",
+ "reference": "2a4ee40acb880c56f29fb1b8886e7ffe94f3b995",
"shasum": ""
},
"require": {
],
"description": "Symfony Yaml Component",
"homepage": "https://symfony.com",
- "time": "2015-12-26 13:37:56"
+ "time": "2016-02-23 07:41:20"
}
],
"aliases": [],
// params hierarchy. More info: http://docs.behat.org/guides/7.config.html
// Example:
// $CFG->behat_config = array(
-// 'default' => array(
-// 'formatter' => array(
-// 'name' => 'pretty',
-// 'parameters' => array(
-// 'decorated' => true,
-// 'verbose' => false
-// )
-// )
-// ),
// 'Mac-Firefox' => array(
+// 'suites' => array (
+// 'default' => array(
+// 'filters' => array(
+// 'tags' => '~@_file_upload'
+// ),
+// ),
+// ),
// 'extensions' => array(
-// 'Behat\MinkExtension\Extension' => array(
+// 'Behat\MinkExtension' => array(
// 'selenium2' => array(
// 'browser' => 'firefox',
// 'capabilities' => array(
// ),
// 'Mac-Safari' => array(
// 'extensions' => array(
-// 'Behat\MinkExtension\Extension' => array(
+// 'Behat\MinkExtension' => array(
// 'selenium2' => array(
// 'browser' => 'safari',
// 'capabilities' => array(
// )
// )
// );
+// You can also use the following config to override default Moodle configuration for Behat.
+// This config is limited to default suite and will be supported in later versions.
+// It will have precedence over $CFG->behat_config.
+// $CFG->behat_profiles = array(
+// 'phantomjs' => array(
+// 'browser' => 'phantomjs',
+// 'tags' => '~@_file_upload&&~@_alert&&~@_bug_phantomjs',
+// 'wd_host' => 'http://127.0.0.1:4443/wd/hub',
+// 'capabilities' => array(
+// 'platform' => 'Linux',
+// 'version' => 2.1
+// )
+// ),
+// );
//
// You can force the browser session (not user's sessions) to restart after N seconds. This could
// be useful if you are using a cloud-based service with time restrictions in the browser side.
--- /dev/null
+<?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/>.
+
+/**
+ * Contains class core_tag\output\course_module_name
+ *
+ * @package core_course
+ * @copyright 2016 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_course\output;
+
+use context_module;
+use lang_string;
+use cm_info;
+
+/**
+ * Class to prepare a course module name for display and in-place editing
+ *
+ * @package core_course
+ * @copyright 2016 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class course_module_name extends \core\output\inplace_editable {
+
+ /** @var cm_info */
+ protected $cm;
+
+ /** @var array */
+ protected $displayoptions;
+
+ /**
+ * Constructor.
+ *
+ * @param cm_info $cm
+ * @param bool $editable
+ * @param array $displayoptions
+ */
+ public function __construct(cm_info $cm, $editable, $displayoptions = array()) {
+ $this->cm = $cm;
+ $this->displayoptions = $displayoptions;
+ $value = $cm->name;
+ $edithint = new lang_string('edittitle');
+ $editlabel = new lang_string('newactivityname', '', $cm->get_formatted_name());
+ $editable = $editable && has_capability('moodle/course:manageactivities',
+ context_module::instance($cm->id));
+ parent::__construct(
+ 'core_course', 'activityname', $cm->id, $editable, $value, $value, $edithint, $editlabel);
+ }
+
+ /**
+ * Export this data so it can be used as the context for a mustache template (core/inplace_editable).
+ *
+ * @param renderer_base $output typically, the renderer that's calling this function
+ * @return array data context for a mustache template
+ */
+ public function export_for_template(\renderer_base $output) {
+ global $PAGE;
+ $courserenderer = $PAGE->get_renderer('core', 'course');
+ $this->displayvalue = $courserenderer->course_section_cm_name_title($this->cm, $this->displayoptions);
+ if (strval($this->displayvalue) === '') {
+ $this->editable = false;
+ }
+ return parent::export_for_template($output);
+ }
+
+ /**
+ * Updates course module name
+ *
+ * @param int $itemid course module id
+ * @param string $newvalue new name
+ * @return static
+ */
+ public static function update($itemid, $newvalue) {
+ list($course, $cm) = get_course_and_cm_from_cmid($itemid);
+ $context = context_module::instance($cm->id);
+ // Check access.
+ require_login($course, false, $cm, true, true);
+ require_capability('moodle/course:manageactivities', $context);
+ // Update value.
+ set_coursemodule_name($cm->id, $newvalue);
+ // Return instance.
+ $cm = get_fast_modinfo($course)->get_cm($cm->id);
+ return new static($cm, true);
+ }
+}
return true;
}
+/**
+ * Changes the course module name
+ *
+ * @param int $id course module id
+ * @param string $name new value for a name
+ * @return bool whether a change was made
+ */
+function set_coursemodule_name($id, $name) {
+ global $CFG, $DB;
+ require_once($CFG->libdir . '/gradelib.php');
+
+ $cm = get_coursemodule_from_id('', $id, 0, false, MUST_EXIST);
+
+ $module = new \stdClass();
+ $module->id = $cm->instance;
+
+ // Escape strings as they would be by mform.
+ if (!empty($CFG->formatstringstriptags)) {
+ $module->name = clean_param($name, PARAM_TEXT);
+ } else {
+ $module->name = clean_param($name, PARAM_CLEANHTML);
+ }
+ if ($module->name === $cm->name || strval($module->name) === '') {
+ return false;
+ }
+ if (\core_text::strlen($module->name) > 255) {
+ throw new \moodle_exception('maximumchars', 'moodle', '', 255);
+ }
+
+ $module->timemodified = time();
+ $DB->update_record($cm->modname, $module);
+ $cm->name = $module->name;
+ \core\event\course_module_updated::create_from_cm($cm)->trigger();
+ rebuild_course_cache($cm->course, true);
+
+ // Attempt to update the grade item if relevant.
+ $grademodule = $DB->get_record($cm->modname, array('id' => $cm->instance));
+ $grademodule->cmidnumber = $cm->idnumber;
+ $grademodule->modname = $cm->modname;
+ grade_update_mod_grades($grademodule);
+
+ return true;
+}
+
/**
* This function will handle the whole deletion process of a module. This includes calling
* the modules delete_instance function, deleting files, events, grades, conditional data,
"Cannot delete this module as the function {$modulename}_delete_instance is missing in mod/$modulename/lib.php.");
}
+ // Allow plugins to use this course module before we completely delete it.
+ if ($pluginsfunction = get_plugins_with_function('pre_course_module_delete')) {
+ foreach ($pluginsfunction as $plugintype => $plugins) {
+ foreach ($plugins as $pluginfunction) {
+ $pluginfunction($cm);
+ }
+ }
+ }
+
// Delete activity context questions and question categories.
question_delete_activity($cm);
return $actions;
}
-/**
- * Returns the rename action.
- *
- * @param cm_info $mod The module to produce editing buttons for
- * @param int $sr The section to link back to (used for creating the links)
- * @return The markup for the rename action, or an empty string if not available.
- */
-function course_get_cm_rename_action(cm_info $mod, $sr = null) {
- global $COURSE, $OUTPUT;
-
- static $str;
- static $baseurl;
-
- $modcontext = context_module::instance($mod->id);
- $hasmanageactivities = has_capability('moodle/course:manageactivities', $modcontext);
-
- if (!isset($str)) {
- $str = get_strings(array('edittitle'));
- }
-
- if (!isset($baseurl)) {
- $baseurl = new moodle_url('/course/mod.php', array('sesskey' => sesskey()));
- }
-
- if ($sr !== null) {
- $baseurl->param('sr', $sr);
- }
-
- // AJAX edit title.
- if ($mod->has_view() && $hasmanageactivities && course_ajax_enabled($COURSE) &&
- (($mod->course == $COURSE->id) || ($mod->course == SITEID))) {
- // we will not display link if we are on some other-course page (where we should not see this module anyway)
- return html_writer::span(
- html_writer::link(
- new moodle_url($baseurl, array('update' => $mod->id)),
- $OUTPUT->pix_icon('t/editstring', '', 'moodle', array('class' => 'iconsmall visibleifjs', 'title' => '')),
- array(
- 'class' => 'editing_title',
- 'data-action' => 'edittitle',
- 'title' => $str->edittitle,
- )
- )
- );
- }
- return '';
-}
-
/**
* Returns the move action.
*
return new core_tag\output\tagindex($tag, 'core', 'course', $content,
$exclusivemode, $fromctx, $ctx, $rec, $page, $totalpages);
}
+
+/**
+ * Implements callback inplace_editable() allowing to edit values in-place
+ *
+ * @param string $itemtype
+ * @param int $itemid
+ * @param mixed $newvalue
+ * @return \core\output\inplace_editable
+ */
+function core_course_inplace_editable($itemtype, $itemid, $newvalue) {
+ if ($itemtype === 'activityname') {
+ return \core_course\output\course_module_name::update($itemid, $newvalue);
+ }
+}
* @return string
*/
public function course_section_cm_name(cm_info $mod, $displayoptions = array()) {
- global $CFG;
+ if ((!$mod->uservisible && empty($mod->availableinfo)) || !$mod->url) {
+ // Nothing to be displayed to the user.
+ return '';
+ }
+
+ // Render element that allows to edit activity name inline. It calls {@link course_section_cm_name_title()}
+ // to get the display title of the activity.
+ $tmpl = new \core_course\output\course_module_name($mod, $this->page->user_is_editing(), $displayoptions);
+ return $this->output->render_from_template('core/inplace_editable', $tmpl->export_for_template($this->output));
+ }
+
+ /**
+ * Renders html to display a name with the link to the course module on a course page
+ *
+ * If module is unavailable for user but still needs to be displayed
+ * in the list, just the name is returned without a link
+ *
+ * Note, that for course modules that never have separate pages (i.e. labels)
+ * this function return an empty string
+ *
+ * @param cm_info $mod
+ * @param array $displayoptions
+ * @return string
+ */
+ public function course_section_cm_name_title(cm_info $mod, $displayoptions = array()) {
$output = '';
if (!$mod->uservisible && empty($mod->availableinfo)) {
- // nothing to be displayed to the user
+ // Nothing to be displayed to the user.
return $output;
}
$url = $mod->url;
$output .= $cmname;
- if ($this->page->user_is_editing()) {
- $output .= ' ' . course_get_cm_rename_action($mod, $sectionreturn);
- }
-
// Module can put text after the link (e.g. forum unread)
$output .= $mod->afterlink;
$isvisible = moveto_module($cm, $section, $beforemod);
&