$DB->set_debug(true);
}
if (!empty($CFG->showcrondebugging)) {
- $CFG->debug = DEBUG_DEVELOPER;
- $CFG->debugdisplay = true;
+ set_debugging(DEBUG_DEVELOPER, true);
}
$starttime = microtime();
$CFG->early_install_lang = true;
$CFG->ostype = (stristr(PHP_OS, 'win') && !stristr(PHP_OS, 'darwin')) ? 'WINDOWS' : 'UNIX';
$CFG->dboptions = array();
+$CFG->debug = (E_ALL | E_STRICT);
+$CFG->debugdisplay = true;
+$CFG->debugdeveloper = true;
$parts = explode('/', str_replace('\\', '/', dirname(dirname(__FILE__))));
$CFG->admin = array_pop($parts);
require_once($CFG->libdir.'/classes/component.php');
require_once($CFG->libdir.'/classes/text.php');
+require_once($CFG->libdir.'/classes/string_manager.php');
+require_once($CFG->libdir.'/classes/string_manager_install.php');
+require_once($CFG->libdir.'/classes/string_manager_standard.php');
require_once($CFG->libdir.'/installlib.php');
require_once($CFG->libdir.'/clilib.php');
require_once($CFG->libdir.'/setuplib.php');
require_once($CFG->libdir.'/adminlib.php');
$confirm = optional_param('confirm', 0, PARAM_BOOL);
+$returnurl = optional_param('returnurl', null, PARAM_LOCALURL);
-admin_externalpage_setup('purgecaches');
-
-require_login();
-require_capability('moodle/site:config', context_system::instance());
-
-if ($confirm) {
- require_sesskey();
+// If we have got here as a confirmed aciton, do it.
+if ($confirm && isloggedin() && confirm_sesskey()) {
+ require_capability('moodle/site:config', context_system::instance());
- // Valid request. Purge, and redisplay the form so it is easy to purge again
- // in the near future.
+ // Valid request. Purge, and redirect the user back to where they came from.
purge_all_caches();
- redirect(new moodle_url('/admin/purgecaches.php'), get_string('purgecachesfinished', 'admin'));
-
-} else {
- // Show a confirm form.
- echo $OUTPUT->header();
- echo $OUTPUT->heading(get_string('purgecaches', 'admin'));
-
- $url = new moodle_url('/admin/purgecaches.php', array('sesskey'=>sesskey(), 'confirm'=>1));
- $button = new single_button($url, get_string('purgecaches','admin'), 'post');
-
- // Cancel button takes them back to the page the were on, if possible,
- // otherwise to the site home page.
- $return = new moodle_url('/');
- if (isset($_SERVER['HTTP_REFERER']) and !empty($_SERVER['HTTP_REFERER'])) {
- if ($_SERVER['HTTP_REFERER'] !== "$CFG->wwwroot/$CFG->admin/purgecaches.php") {
- $return = $_SERVER['HTTP_REFERER'];
- }
+
+ if ($returnurl) {
+ $returnurl = $CFG->wwwroot . $returnurl;
+ } else {
+ $returnurl = new moodle_url('/admin/purgecaches.php');
}
+ redirect($returnurl, get_string('purgecachesfinished', 'admin'));
+}
- echo $OUTPUT->confirm(get_string('purgecachesconfirm', 'admin'), $button, $return);
- echo $OUTPUT->footer();
+// Otherwise, show a button to actually purge the caches.
+admin_externalpage_setup('purgecaches');
+
+$actionurl = new moodle_url('/admin/purgecaches.php', array('sesskey'=>sesskey(), 'confirm'=>1));
+if ($returnurl) {
+ $actionurl->param('returnurl', $returnurl);
}
+
+echo $OUTPUT->header();
+echo $OUTPUT->heading(get_string('purgecaches', 'admin'));
+
+echo $OUTPUT->box_start('generalbox', 'notice');
+echo html_writer::tag('p', get_string('purgecachesconfirm', 'admin'));
+echo $OUTPUT->single_button($actionurl, get_string('purgecaches', 'admin'), 'post');
+echo $OUTPUT->box_end();
+
+echo $OUTPUT->footer();
if (optional_param('submit', false, PARAM_BOOL) && data_submitted() && confirm_sesskey()) {
$controller->process_submission();
$syscontext->mark_dirty();
- add_to_log(SITEID, 'role', 'edit allow ' . $mode, str_replace($CFG->wwwroot . '/', '', $baseurl), '', '', $USER->id);
+ $event = null;
+ // Create event depending on mode.
+ switch ($mode) {
+ case 'assign':
+ $event = \core\event\role_allow_assign_updated::create(array('context' => $syscontext));
+ break;
+ case 'override':
+ $event = \core\event\role_allow_override_updated::create(array('context' => $syscontext));
+ break;
+ case 'switch':
+ $event = \core\event\role_allow_switch_updated::create(array('context' => $syscontext));
+ break;
+ }
+ if ($event) {
+ $event->trigger();
+ }
redirect($baseurl);
}
$potentialuserselector->invalidate_selected_users();
$currentuserselector->invalidate_selected_users();
- $rolename = $assignableroles[$roleid];
- add_to_log($course->id, 'role', 'assign', 'admin/roles/assign.php?contextid='.$context->id.'&roleid='.$roleid, $rolename, '', $USER->id);
// Counts have changed, so reload.
list($assignableroles, $assigncounts, $nameswithcounts) = get_assignable_roles($context, ROLENAME_BOTH, true);
}
$potentialuserselector->invalidate_selected_users();
$currentuserselector->invalidate_selected_users();
- $rolename = $assignableroles[$roleid];
- add_to_log($course->id, 'role', 'unassign', 'admin/roles/assign.php?contextid='.$context->id.'&roleid='.$roleid, $rolename, '', $USER->id);
// Counts have changed, so reload.
list($assignableroles, $assigncounts, $nameswithcounts) = get_assignable_roles($context, ROLENAME_BOTH, true);
}
// Process submission in necessary.
if (optional_param('savechanges', false, PARAM_BOOL) && confirm_sesskey() && $definitiontable->is_submission_valid()) {
$definitiontable->save_changes();
- add_to_log(SITEID, 'role', $action, 'admin/roles/define.php?action=view&roleid=' .
- $definitiontable->get_role_id(), $definitiontable->get_role_name(), '', $USER->id);
+ $tableroleid = $definitiontable->get_role_id();
+ // Trigger event.
+ $event = \core\event\role_capabilities_updated::create(
+ array(
+ 'context' => $systemcontext,
+ 'objectid' => $roleid,
+ 'other' => array('name' => $definitiontable->get_role_name())
+ )
+ );
+ $event->set_legacy_logdata(array(SITEID, 'role', $action, 'admin/roles/define.php?action=view&roleid=' . $tableroleid,
+ $definitiontable->get_role_name(), '', $USER->id));
+ if (!empty($role)) {
+ $event->add_record_snapshot('role', $role);
+ }
+ $event->trigger();
+
if ($action === 'add') {
redirect(new moodle_url('/admin/roles/define.php', array('action'=>'view', 'roleid'=>$definitiontable->get_role_id())));
} else {
}
// Deleted a role sitewide...
$systemcontext->mark_dirty();
- add_to_log(SITEID, 'role', 'delete', 'admin/roles/manage.php', $roles[$roleid]->localname, '', $USER->id);
redirect($baseurl);
break;
if (optional_param('savechanges', false, PARAM_BOOL) && confirm_sesskey()) {
$overridestable->save_changes();
$rolename = $overridableroles[$roleid];
- add_to_log($course->id, 'role', 'override', 'admin/roles/override.php?contextid='.$context->id.'&roleid='.$roleid, $rolename, '', $USER->id);
+ // Trigger event.
+ $event = \core\event\role_capabilities_updated::create(
+ array(
+ 'context' => $context,
+ 'objectid' => $roleid,
+ 'courseid' => $courseid,
+ 'other' => array('name' => $rolename)
+ )
+ );
+
+ $event->set_legacy_logdata(
+ array(
+ $course->id, 'role', 'override', 'admin/roles/override.php?contextid=' . $context->id . '&roleid=' . $roleid,
+ $rolename, '', $USER->id
+ )
+ );
+ $event->add_record_snapshot('role', $role);
+ $event->trigger();
+
redirect($returnurl);
}
// "performance" settingpage
$temp = new admin_settingpage('performance', new lang_string('performance', 'admin'));
+// Memory limit options for large administration tasks.
+$memoryoptions = array(
+ '64M' => '64M',
+ '128M' => '128M',
+ '256M' => '256M',
+ '512M' => '512M',
+ '1024M' => '1024M',
+ '2048M' => '2048M');
+
+// Allow larger memory usage for 64-bit sites only.
+if (PHP_INT_SIZE === 8) {
+ $memoryoptions['3072M'] = '3072M';
+ $memoryoptions['4096M'] = '4096M';
+}
+
$temp->add(new admin_setting_configselect('extramemorylimit', new lang_string('extramemorylimit', 'admin'),
new lang_string('configextramemorylimit', 'admin'), '512M',
- // if this option is set to 0, default 128M will be used
- array( '64M' => '64M',
- '128M' => '128M',
- '256M' => '256M',
- '512M' => '512M',
- '1024M' => '1024M',
- '2048M' => '2048M',
- )));
+ $memoryoptions));
+
$temp->add(new admin_setting_configtext('curlcache', new lang_string('curlcache', 'admin'),
new lang_string('configcurlcache', 'admin'), 120, PARAM_INT));
And I should see "Grouping 1"
And I should see "Grouping 2"
+ @javascript
+ Scenario: Role overrides
+ Given the following "users" exists:
+ | username | firstname | lastname | email |
+ | teacher1 | Teacher | 1 | teacher1@asd.com |
+ | student1 | Student | 1 | student1@asd.com |
+ And the following "categories" exists:
+ | name | category | idnumber |
+ | Cat 1 | 0 | CAT1 |
+ And the following "courses" exists:
+ | fullname | shortname |
+ | Course 1 | C1 |
+ And the following "course enrolments" exists:
+ | user | course | role |
+ | student1 | C1 | student |
+ | teacher1 | C1 | editingteacher |
+ And the following "permission overrides" exists:
+ | capability | permission | role | contextlevel | reference |
+ | mod/forum:editanypost | Allow | student | Course | C1 |
+ | mod/forum:replynews | Prevent | editingteacher | Course | C1 |
+ When I log in as "admin"
+ And I follow "Course 1"
+ And I expand "Users" node
+ And I follow "Permissions"
+ And I select "Student (1)" from "Advanced role override"
+ Then the "mod/forum:editanypost" field should match "1" value
+ And I press "Cancel"
+ And I select "Teacher (1)" from "Advanced role override"
+ And the "mod/forum:replynews" field should match "-1" value
+ And I press "Cancel"
+
Scenario: Add course enrolments
Given the following "users" exists:
| username | firstname | lastname | email |
--- /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/>.
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Backend code for the 'make large course' tool.
+ *
+ * @package tool_generator
+ * @copyright 2013 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class tool_generator_backend {
+ /**
+ * @var int Lowest (smallest) size index
+ */
+ const MIN_SIZE = 0;
+ /**
+ * @var int Highest (largest) size index
+ */
+ const MAX_SIZE = 5;
+ /**
+ * @var int Default size index
+ */
+ const DEFAULT_SIZE = 3;
+
+ /**
+ * @var array Number of sections in course
+ */
+ private static $paramsections = array(1, 10, 100, 500, 1000, 2000);
+ /**
+ * @var array Number of Page activities in course
+ */
+ private static $parampages = array(1, 50, 200, 1000, 5000, 10000);
+ /**
+ * @var array Number of students enrolled in course
+ */
+ private static $paramusers = array(1, 100, 1000, 10000, 50000, 100000);
+ /**
+ * Total size of small files: 1KB, 1MB, 10MB, 100MB, 1GB, 2GB.
+ *
+ * @var array Number of small files created in a single file activity
+ */
+ private static $paramsmallfilecount = array(1, 64, 128, 1024, 16384, 32768);
+ /**
+ * @var array Size of small files (to make the totals into nice numbers)
+ */
+ private static $paramsmallfilesize = array(1024, 16384, 81920, 102400, 65536, 65536);
+ /**
+ * Total size of big files: 8KB, 8MB, 80MB, 800MB, 8GB, 16GB.
+ *
+ * @var array Number of big files created as individual file activities
+ */
+ private static $parambigfilecount = array(1, 2, 5, 10, 10, 10);
+ /**
+ * @var array Size of each large file
+ */
+ private static $parambigfilesize = array(8192, 4194304, 16777216, 83886080,
+ 858993459, 1717986918);
+ /**
+ * @var array Number of forum discussions
+ */
+ private static $paramforumdiscussions = array(1, 10, 100, 500, 1000, 2000);
+ /**
+ * @var array Number of forum posts per discussion
+ */
+ private static $paramforumposts = array(2, 2, 5, 10, 10, 10);
+
+ /**
+ * @var string Course shortname
+ */
+ private $shortname;
+
+ /**
+ * @var int Size code (index in the above arrays)
+ */
+ private $size;
+
+ /**
+ * @var bool True if displaying progress
+ */
+ private $progress;
+
+ /**
+ * @var testing_data_generator Data generator
+ */
+ private $generator;
+
+ /**
+ * @var stdClass Course object
+ */
+ private $course;
+
+ /**
+ * @var int Epoch time at which last dot was displayed
+ */
+ private $lastdot;
+
+ /**
+ * @var int Epoch time at which last percentage was displayed
+ */
+ private $lastpercentage;
+
+ /**
+ * @var int Epoch time at which current step (current set of dots) started
+ */
+ private $starttime;
+
+ /**
+ * @var array Array from test user number (1...N) to userid in database
+ */
+ private $userids;
+
+ /**
+ * Constructs object ready to create course.
+ *
+ * @param string $shortname Course shortname
+ * @param int $size Size as numeric index
+ * @param bool $progress True if progress information should be displayed
+ * @return int Course id
+ * @throws coding_exception If parameters are invalid
+ */
+ public function __construct($shortname, $size, $progress = true) {
+ // Check parameter.
+ if ($size < self::MIN_SIZE || $size > self::MAX_SIZE) {
+ throw new coding_exception('Invalid size');
+ }
+
+ // Set parameters.
+ $this->shortname = $shortname;
+ $this->size = $size;
+ $this->progress = $progress;
+ }
+
+ /**
+ * Gets a list of size choices supported by this backend.
+ *
+ * @return array List of size (int) => text description for display
+ */
+ public static function get_size_choices() {
+ $options = array();
+ for ($size = self::MIN_SIZE; $size <= self::MAX_SIZE; $size++) {
+ $options[$size] = get_string('size_' . $size, 'tool_generator');
+ }
+ return $options;
+ }
+
+ /**
+ * Converts a size name into the numeric constant.
+ *
+ * @param string $sizename Size name e.g. 'L'
+ * @return int Numeric version
+ * @throws coding_exception If the size name is not known
+ */
+ public static function size_for_name($sizename) {
+ for ($size = self::MIN_SIZE; $size <= self::MAX_SIZE; $size++) {
+ if ($sizename == get_string('shortsize_' . $size, 'tool_generator')) {
+ return $size;
+ }
+ }
+ throw new coding_exception("Unknown size name '$sizename'");
+ }
+
+ /**
+ * Checks that a shortname is available (unused).
+ *
+ * @param string $shortname Proposed course shortname
+ * @return string An error message if the name is unavailable or '' if OK
+ */
+ public static function check_shortname_available($shortname) {
+ global $DB;
+ $fullname = $DB->get_field('course', 'fullname',
+ array('shortname' => $shortname), IGNORE_MISSING);
+ if ($fullname !== false) {
+ // I wanted to throw an exception here but it is not possible to
+ // use strings from moodle.php in exceptions, and I didn't want
+ // to duplicate the string in tool_generator, so I changed this to
+ // not use exceptions.
+ return get_string('shortnametaken', 'moodle', $fullname);
+ }
+ return '';
+ }
+
+ /**
+ * Runs the entire 'make' process.
+ *
+ * @return int Course id
+ */
+ public function make() {
+ global $DB, $CFG;
+ require_once($CFG->dirroot . '/lib/phpunit/classes/util.php');
+
+ raise_memory_limit(MEMORY_EXTRA);
+
+ if ($this->progress && !CLI_SCRIPT) {
+ echo html_writer::start_tag('ul');
+ }
+
+ $entirestart = microtime(true);
+
+ // Start transaction.
+ $transaction = $DB->start_delegated_transaction();
+
+ // Get generator.
+ $this->generator = phpunit_util::get_data_generator();
+
+ // Make course.
+ $this->course = $this->create_course();
+ $this->create_users();
+ $this->create_pages();
+ $this->create_small_files();
+ $this->create_big_files();
+ $this->create_forum();
+
+ // Log total time.
+ $this->log('complete', round(microtime(true) - $entirestart, 1));
+
+ if ($this->progress && !CLI_SCRIPT) {
+ echo html_writer::end_tag('ul');
+ }
+
+ // Commit transaction and finish.
+ $transaction->allow_commit();
+ return $this->course->id;
+ }
+
+ /**
+ * Creates the actual course.
+ *
+ * @return stdClass Course record
+ */
+ private function create_course() {
+ $this->log('createcourse', $this->shortname);
+ $courserecord = array('shortname' => $this->shortname,
+ 'fullname' => get_string('fullname', 'tool_generator',
+ array('size' => get_string('shortsize_' . $this->size, 'tool_generator'))),
+ 'numsections' => self::$paramsections[$this->size]);
+ return $this->generator->create_course($courserecord, array('createsections' => true));
+ }
+
+ /**
+ * Creates a number of user accounts and enrols them on the course.
+ * Note: Existing user accounts that were created by this system are
+ * reused if available.
+ */
+ private function create_users() {
+ global $DB;
+
+ // Work out total number of users.
+ $count = self::$paramusers[$this->size];
+
+ // Get existing users in order. We will 'fill up holes' in this up to
+ // the required number.
+ $this->log('checkaccounts', $count);
+ $nextnumber = 1;
+ $rs = $DB->get_recordset_select('user', $DB->sql_like('username', '?'),
+ array('tool_generator_%'), 'username', 'id, username');
+ foreach ($rs as $rec) {
+ // Extract number from username.
+ $matches = array();
+ if (!preg_match('~^tool_generator_([0-9]{6})$~', $rec->username, $matches)) {
+ continue;
+ }
+ $number = (int)$matches[1];
+
+ // Create missing users in range up to this.
+ if ($number != $nextnumber) {
+ $this->create_user_accounts($nextnumber, min($number - 1, $count));
+ } else {
+ $this->userids[$number] = (int)$rec->id;
+ }
+
+ // Stop if we've got enough users.
+ $nextnumber = $number + 1;
+ if ($number >= $count) {
+ break;
+ }
+ }
+ $rs->close();
+
+ // Create users from end of existing range.
+ if ($nextnumber <= $count) {
+ $this->create_user_accounts($nextnumber, $count);
+ }
+
+ // Assign all users to course.
+ $this->log('enrol', $count, true);
+
+ $enrolplugin = enrol_get_plugin('manual');
+ $instances = enrol_get_instances($this->course->id, true);
+ foreach ($instances as $instance) {
+ if ($instance->enrol === 'manual') {
+ break;
+ }
+ }
+ if ($instance->enrol !== 'manual') {
+ throw new coding_exception('No manual enrol plugin in course');
+ }
+ $role = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);
+
+ for ($number = 1; $number <= $count; $number++) {
+ // Enrol user.
+ $enrolplugin->enrol_user($instance, $this->userids[$number], $role->id);
+ $this->dot($number, $count);
+ }
+
+ $this->end_log();
+ }
+
+ /**
+ * Creates user accounts with a numeric range.
+ *
+ * @param int $first Number of first user
+ * @param int $last Number of last user
+ */
+ private function create_user_accounts($first, $last) {
+ $this->log('createaccounts', (object)array('from' => $first, 'to' => $last), true);
+ $count = $last - $first + 1;
+ $done = 0;
+ for ($number = $first; $number <= $last; $number++, $done++) {
+ // Work out username with 6-digit number.
+ $textnumber = (string)$number;
+ while (strlen($textnumber) < 6) {
+ $textnumber = '0' . $textnumber;
+ }
+ $username = 'tool_generator_' . $textnumber;
+
+ // Create user account.
+ $record = array('firstname' => get_string('firstname', 'tool_generator'),
+ 'lastname' => $number, 'username' => $username);
+ $user = $this->generator->create_user($record);
+ $this->userids[$number] = (int)$user->id;
+ $this->dot($done, $count);
+ }
+ $this->end_log();
+ }
+
+ /**
+ * Creates a number of Page activities.
+ */
+ private function create_pages() {
+ // Set up generator.
+ $pagegenerator = $this->generator->get_plugin_generator('mod_page');
+
+ // Create pages.
+ $number = self::$parampages[$this->size];
+ $this->log('createpages', $number, true);
+ for ($i=0; $i<$number; $i++) {
+ $record = array('course' => $this->course->id);
+ $options = array('section' => $this->get_random_section());
+ $pagegenerator->create_instance($record, $options);
+ $this->dot($i, $number);
+ }
+
+ $this->end_log();
+ }
+
+ /**
+ * Creates one resource activity with a lot of small files.
+ */
+ private function create_small_files() {
+ $count = self::$paramsmallfilecount[$this->size];
+ $this->log('createsmallfiles', $count, true);
+
+ // Create resource with default textfile only.
+ $resourcegenerator = $this->generator->get_plugin_generator('mod_resource');
+ $record = array('course' => $this->course->id,
+ 'name' => get_string('smallfiles', 'tool_generator'));
+ $options = array('section' => 0);
+ $resource = $resourcegenerator->create_instance($record, $options);
+
+ // Add files.
+ $fs = get_file_storage();
+ $context = context_module::instance($resource->cmid);
+ $filerecord = array('component' => 'mod_resource', 'filearea' => 'content',
+ 'contextid' => $context->id, 'itemid' => 0, 'filepath' => '/');
+ for ($i = 0; $i < $count; $i++) {
+ $filerecord['filename'] = 'smallfile' . $i . '.dat';
+
+ // Generate random binary data (different for each file so it
+ // doesn't compress unrealistically).
+ $data = self::get_random_binary(self::$paramsmallfilesize[$this->size]);
+
+ $fs->create_file_from_string($filerecord, $data);
+ $this->dot($i, $count);
+ }
+
+ $this->end_log();
+ }
+
+ /**
+ * Creates a string of random binary data. The start of the string includes
+ * the current time, in an attempt to avoid large-scale repetition.
+ *
+ * @param int $length Number of bytes
+ * @return Random data
+ */
+ private static function get_random_binary($length) {
+ $data = microtime(true);
+ if (strlen($data) > $length) {
+ // Use last digits of data.
+ return substr($data, -$length);
+ }
+ $length -= strlen($data);
+ for ($j=0; $j < $length; $j++) {
+ $data .= chr(rand(1, 255));
+ }
+ return $data;
+ }
+
+ /**
+ * Creates a number of resource activities with one big file each.
+ */
+ private function create_big_files() {
+ global $CFG;
+
+ // Work out how many files and how many blocks to use (up to 64KB).
+ $count = self::$parambigfilecount[$this->size];
+ $blocks = ceil(self::$parambigfilesize[$this->size] / 65536);
+ $blocksize = floor(self::$parambigfilesize[$this->size] / $blocks);
+
+ $this->log('createbigfiles', $count, true);
+
+ // Prepare temp area.
+ $tempfolder = make_temp_directory('tool_generator');
+ $tempfile = $tempfolder . '/' . rand();
+
+ // Create resources and files.
+ $fs = get_file_storage();
+ $resourcegenerator = $this->generator->get_plugin_generator('mod_resource');
+ for ($i = 0; $i < $count; $i++) {
+ // Create resource.
+ $record = array('course' => $this->course->id,
+ 'name' => get_string('bigfile', 'tool_generator', $i));
+ $options = array('section' => $this->get_random_section());
+ $resource = $resourcegenerator->create_instance($record, $options);
+
+ // Write file.
+ $handle = fopen($tempfile, 'w');
+ if (!$handle) {
+ throw new coding_exception('Failed to open temporary file');
+ }
+ for ($j = 0; $j < $blocks; $j++) {
+ $data = self::get_random_binary($blocksize);
+ fwrite($handle, $data);
+ $this->dot($i * $blocks + $j, $count * $blocks);
+ }
+ fclose($handle);
+
+ // Add file.
+ $context = context_module::instance($resource->cmid);
+ $filerecord = array('component' => 'mod_resource', 'filearea' => 'content',
+ 'contextid' => $context->id, 'itemid' => 0, 'filepath' => '/',
+ 'filename' => 'bigfile' . $i . '.dat');
+ $fs->create_file_from_pathname($filerecord, $tempfile);
+ }
+
+ unlink($tempfile);
+ $this->end_log();
+ }
+
+ /**
+ * Creates one forum activity with a bunch of posts.
+ */
+ private function create_forum() {
+ global $DB;
+
+ $discussions = self::$paramforumdiscussions[$this->size];
+ $posts = self::$paramforumposts[$this->size];
+ $totalposts = $discussions * $posts;
+
+ $this->log('createforum', $totalposts, true);
+
+ // Create empty forum.
+ $forumgenerator = $this->generator->get_plugin_generator('mod_forum');
+ $record = array('course' => $this->course->id,
+ 'name' => get_string('pluginname', 'forum'));
+ $options = array('section' => 0);
+ $forum = $forumgenerator->create_instance($record, $options);
+
+ // Add discussions and posts.
+ $sofar = 0;
+ for ($i=0; $i < $discussions; $i++) {
+ $record = array('forum' => $forum->id, 'course' => $this->course->id,
+ 'userid' => $this->get_random_user());
+ $discussion = $forumgenerator->create_discussion($record);
+ $parentid = $DB->get_field('forum_posts', 'id', array('discussion' => $discussion->id), MUST_EXIST);
+ $sofar++;
+ for ($j=0; $j < $posts - 1; $j++, $sofar++) {
+ $record = array('discussion' => $discussion->id,
+ 'userid' => $this->get_random_user(), 'parent' => $parentid);
+ $forumgenerator->create_post($record);
+ $this->dot($sofar, $totalposts);
+ }
+ }
+
+ $this->end_log();
+ }
+
+ /**
+ * Gets a random section number.
+ *
+ * @return int A section number from 1 to the number of sections
+ */
+ private function get_random_section() {
+ return rand(1, self::$paramsections[$this->size]);
+ }
+
+ /**
+ * Gets a random user id.
+ *
+ * @return int A user id for a random created user
+ */
+ private function get_random_user() {
+ return $this->userids[rand(1, self::$paramusers[$this->size])];
+ }
+
+ /**
+ * Displays information as part of progress.
+ * @param string $langstring Part of langstring (after progress_)
+ * @param mixed $a Optional lang string parameters
+ * @param bool $leaveopen If true, doesn't close LI tag (ready for dots)
+ */
+ private function log($langstring, $a = null, $leaveopen = false) {
+ if (!$this->progress) {
+ return;
+ }
+ if (CLI_SCRIPT) {
+ echo '* ';
+ } else {
+ echo html_writer::start_tag('li');
+ }
+ echo get_string('progress_' . $langstring, 'tool_generator', $a);
+ if (!$leaveopen) {
+ if (CLI_SCRIPT) {
+ echo "\n";
+ } else {
+ echo html_writer::end_tag('li');
+ }
+ } else {
+ echo ': ';
+ $this->lastdot = time();
+ $this->lastpercentage = $this->lastdot;
+ $this->starttime = microtime(true);
+ }
+ }
+
+ /**
+ * Outputs dots. There is up to one dot per second. Once a minute, it
+ * displays a percentage.
+ * @param int $number Number of completed items
+ * @param int $total Total number of items to complete
+ */
+ private function dot($number, $total) {
+ if (!$this->progress) {
+ return;
+ }
+ $now = time();
+ if ($now == $this->lastdot) {
+ return;
+ }
+ $this->lastdot = $now;
+ if (CLI_SCRIPT) {
+ echo '.';
+ } else {
+ echo ' . ';
+ }
+ if ($now - $this->lastpercentage >= 30) {
+ echo round(100.0 * $number / $total, 1) . '%';
+ $this->lastpercentage = $now;
+ }
+
+ // Update time limit so PHP doesn't time out.
+ if (!CLI_SCRIPT) {
+ set_time_limit(120);
+ }
+ }
+
+ /**
+ * Ends a log string that was started using log function with $leaveopen.
+ */
+ private function end_log() {
+ if (!$this->progress) {
+ return;
+ }
+ echo get_string('done', 'tool_generator', round(microtime(true) - $this->starttime, 1));
+ if (CLI_SCRIPT) {
+ echo "\n";
+ } else {
+ echo html_writer::end_tag('li');
+ }
+ }
+}
--- /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/>.
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->libdir . '/formslib.php');
+
+/**
+ * Form with options for creating large course.
+ *
+ * @package tool_generator
+ * @copyright 2013 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class tool_generator_make_form extends moodleform {
+
+ public function definition() {
+ $mform = $this->_form;
+
+ $mform->addElement('select', 'size', get_string('size', 'tool_generator'),
+ tool_generator_backend::get_size_choices());
+ $mform->setDefault('size', tool_generator_backend::DEFAULT_SIZE);
+
+ $mform->addElement('text', 'shortname', get_string('shortnamecourse'));
+ $mform->addRule('shortname', get_string('missingshortname'), 'required', null, 'client');
+ $mform->setType('shortname', PARAM_TEXT);
+
+ $mform->addElement('submit', 'submit', get_string('createcourse', 'tool_generator'));
+ }
+
+ public function validation($data, $files) {
+ global $DB;
+ $errors = array();
+
+ // Check course doesn't already exist.
+ if (!empty($data['shortname'])) {
+ // Check shortname.
+ $error = tool_generator_backend::check_shortname_available($data['shortname']);
+ if ($error) {
+ $errors['shortname'] = $error;
+ }
+ }
+
+ return $errors;
+ }
+}
require(dirname(__FILE__) . '/../../../../config.php');
require_once(dirname(__FILE__) . '/../locallib.php');
-if (!debugging('', DEBUG_DEVELOPER)) {
+if (!$CFG->debugdeveloper) {
echo("This script is for developers only!!!\n");
exit(1);
}
--- /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/>.
+
+/**
+ * CLI interface for creating a test course.
+ *
+ * @package tool_generator
+ * @copyright 2013 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+define('CLI_SCRIPT', true);
+define('NO_OUTPUT_BUFFERING', true);
+
+require(dirname(__FILE__) . '/../../../../config.php');
+require_once($CFG->libdir. '/clilib.php');
+
+// CLI options.
+list($options, $unrecognized) = cli_get_params(
+ array(
+ 'help' => false,
+ 'shortname' => false,
+ 'size' => false,
+ 'bypasscheck' => false,
+ 'quiet' => false
+ ),
+ array(
+ 'h' => 'help'
+ )
+);
+
+// Display help.
+if (!empty($options['help']) || empty($options['shortname']) || empty($options['size'])) {
+ echo "
+Utility to create standard test course. (Also available in GUI interface.)
+
+Not for use on live sites; only normally works if debugging is set to DEVELOPER
+level.
+
+Options:
+--shortname Shortname of course to create (required)
+--size Size of course to create XS, S, M, L, XL, or XXL (required)
+--bypasscheck Bypasses the developer-mode check (be careful!)
+--quiet Do not show any output
+
+-h, --help Print out this help
+
+Example from Moodle root directory:
+\$ php admin/tool/generator/cli/maketestcourse.php --shortname=SIZE_S --size=S
+";
+ // Exit with error unless we're showing this because they asked for it.
+ exit(empty($options['help']) ? 1 : 0);
+}
+
+// Check debugging is set to developer level.
+if (empty($options['bypasscheck']) && !debugging('', DEBUG_DEVELOPER)) {
+ cli_error(get_string('error_notdebugging', 'tool_generator'));
+}
+
+// Get options.
+$shortname = $options['shortname'];
+$sizename = $options['size'];
+
+// Check size.
+try {
+ $size = tool_generator_backend::size_for_name($sizename);
+} catch (coding_exception $e) {
+ cli_error("Invalid size ($sizename). Use --help for help.");
+}
+
+// Check shortname.
+if ($error = tool_generator_backend::check_shortname_available($shortname)) {
+ cli_error($error);
+}
+
+// Switch to admin user account.
+session_set_user(get_admin());
+
+// Do backend code to generate course.
+$backend = new tool_generator_backend($shortname, $size, empty($options['quiet']));
+$id = $backend->make();
error('Only for admins');
}
-if (!debugging('', DEBUG_DEVELOPER)) {
+if (!$CFG->debugdeveloper) {
error('This script is for developers only!!!');
}
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
- * Strings for component 'tool_generator', language 'en', branch 'MOODLE_22_STABLE'
+ * Language strings.
*
- * @package tool
- * @subpackage generator
- * @copyright 2011 Petr Skoda
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @package tool_generator
+ * @copyright 2013 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
+$string['bigfile'] = 'Big file {$a}';
+$string['createcourse'] = 'Create course';
+$string['creating'] = 'Creating course';
+$string['done'] = 'done ({$a}s)';
+$string['explanation'] = 'This tool creates standard test courses that include many
+sections, activities, and files.
+
+This is intended to provide a standardised measure for checking the reliability
+and performance of various system components (such as backup and restore).
+
+This test is important because there have been many cases previously where,
+faced with real-life use cases (e.g. a course with 1,000 activities), the system
+does not work.
+
+Courses created using this feature can occupy a large amount of database and
+filesystem space (tens of gigabytes). You will need to delete the courses
+(and wait for various cleanup runs) to release this space again.
+
+**Do not use this feature on a live system**. Use only on a developer server.
+(To avoid accidental use, this feature is disabled unless you have also selected
+DEVELOPER debugging level.)';
+
+$string['error_notdebugging'] = 'Not available on this server because debugging is not set to DEVELOPER';
+$string['firstname'] = 'Test course user';
+$string['fullname'] = 'Test course: {$a->size}';
+$string['maketestcourse'] = 'Make test course';
$string['pluginname'] = 'Random course generator';
+$string['progress_createcourse'] = 'Creating course {$a}';
+$string['progress_checkaccounts'] = 'Checking user accounts ({$a})';
+$string['progress_createaccounts'] = 'Creating user accounts ({$a->from} - {$a->to})';
+$string['progress_createbigfiles'] = 'Creating big files ({$a})';
+$string['progress_createforum'] = 'Creating forum ({$a} posts)';
+$string['progress_createpages'] = 'Creating pages ({$a})';
+$string['progress_createsmallfiles'] = 'Creating small files ({$a})';
+$string['progress_enrol'] = 'Enrolling users into course ({$a})';
+$string['progress_complete'] = 'Complete ({$a}s)';
+$string['shortsize_0'] = 'XS';
+$string['shortsize_1'] = 'S';
+$string['shortsize_2'] = 'M';
+$string['shortsize_3'] = 'L';
+$string['shortsize_4'] = 'XL';
+$string['shortsize_5'] = 'XXL';
+$string['size'] = 'Size of course';
+$string['size_0'] = 'XS (~10KB; create in ~1 second)';
+$string['size_1'] = 'S (~10MB; create in ~30 seconds)';
+$string['size_2'] = 'M (~100MB; create in ~5 minutes)';
+$string['size_3'] = 'L (~1GB; create in ~1 hour)';
+$string['size_4'] = 'XL (~10GB; create in ~4 hours)';
+$string['size_5'] = 'XXL (~20GB; create in ~8 hours)';
+$string['smallfiles'] = 'Small files';
--- /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/>.
+
+/**
+ * Script creates a standardised large course for testing reliability and
+ * performance.
+ *
+ * @package tool_generator
+ * @copyright 2013 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+// Disable buffering so that the progress output displays gradually without
+// needing to call flush().
+define('NO_OUTPUT_BUFFERING', true);
+
+require('../../../config.php');
+
+require_once($CFG->libdir . '/adminlib.php');
+
+// Initialise page and check permissions.
+admin_externalpage_setup('toolgenerator');
+
+// Start page.
+echo $OUTPUT->header();
+echo $OUTPUT->heading(get_string('maketestcourse', 'tool_generator'));
+
+// Information message.
+$context = context_system::instance();
+echo $OUTPUT->box(format_text(get_string('explanation', 'tool_generator'),
+ FORMAT_MARKDOWN, array('context' => $context)));
+
+// Check debugging is set to DEVELOPER.
+if (!debugging('', DEBUG_DEVELOPER)) {
+ echo $OUTPUT->notification(get_string('error_notdebugging', 'tool_generator'));
+ echo $OUTPUT->footer();
+ exit;
+}
+
+// Set up the form.
+$mform = new tool_generator_make_form('maketestcourse.php');
+if ($data = $mform->get_data()) {
+ // Do actual work.
+ echo $OUTPUT->heading(get_string('creating', 'tool_generator'));
+ $backend = new tool_generator_backend($data->shortname, $data->size);
+ $id = $backend->make();
+
+ echo html_writer::div(
+ html_writer::link(new moodle_url('/course/view.php', array('id' => $id)),
+ get_string('continue')));
+} else {
+ // Display form.
+ $mform->display();
+}
+
+// Finish page.
+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/>.
+
+/**
+ * Admin settings.
+ *
+ * @package tool_generator
+ * @copyright 2013 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die;
+
+if ($hassiteconfig) {
+ $ADMIN->add('development', new admin_externalpage('toolgenerator',
+ get_string('maketestcourse', 'tool_generator'),
+ $CFG->wwwroot . '/' . $CFG->admin . '/tool/generator/maketestcourse.php'));
+}
+
--- /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/>.
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Automated unit testing. This tests the 'make large course' backend,
+ * using the 'XS' option so that it completes quickly.
+ *
+ * @package tool_generator
+ * @copyright 2013 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class tool_generator_maketestcourse_testcase extends advanced_testcase {
+ /**
+ * Creates a small test course and checks all the components have been put in place.
+ */
+ public function test_make_xs_course() {
+ global $DB;
+
+ $this->resetAfterTest();
+ $this->setAdminUser();
+
+ // Create the XS course.
+ $backend = new tool_generator_backend('TOOL_MAKELARGECOURSE_XS', 0, false);
+ $courseid = $backend->make();
+
+ // Get course details.
+ $course = get_course($courseid);
+ $context = context_course::instance($courseid);
+ $modinfo = get_fast_modinfo($course);
+
+ // Check sections (just section 0 plus one other).
+ $this->assertEquals(2, count($modinfo->get_section_info_all()));
+
+ // Check user is enrolled.
+ $users = get_enrolled_users($context);
+ $this->assertEquals(1, count($users));
+ $this->assertEquals('tool_generator_000001', reset($users)->username);
+
+ // Check there's a page on the course.
+ $pages = $modinfo->get_instances_of('page');
+ $this->assertEquals(1, count($pages));
+
+ // Check there are small files.
+ $resources = $modinfo->get_instances_of('resource');
+ $ok = false;
+ foreach ($resources as $resource) {
+ if ($resource->sectionnum == 0) {
+ // The one in section 0 is the 'small files' resource.
+ $ok = true;
+ break;
+ }
+ }
+ $this->assertTrue($ok);
+
+ // Check it contains 2 files (the default txt and a dat file).
+ $fs = get_file_storage();
+ $resourcecontext = context_module::instance($resource->id);
+ $files = $fs->get_area_files($resourcecontext->id, 'mod_resource', 'content', false, 'filename', false);
+ $files = array_values($files);
+ $this->assertEquals(2, count($files));
+ $this->assertEquals('resource1.txt', $files[0]->get_filename());
+ $this->assertEquals('smallfile0.dat', $files[1]->get_filename());
+
+ // Check there's a single 'big' file (it's actually only 8KB).
+ $ok = false;
+ foreach ($resources as $resource) {
+ if ($resource->sectionnum == 1) {
+ $ok = true;
+ break;
+ }
+ }
+ $this->assertTrue($ok);
+
+ // Check it contains 2 files.
+ $resourcecontext = context_module::instance($resource->id);
+ $files = $fs->get_area_files($resourcecontext->id, 'mod_resource', 'content', false, 'filename', false);
+ $files = array_values($files);
+ $this->assertEquals(2, count($files));
+ $this->assertEquals('bigfile0.dat', $files[0]->get_filename());
+ $this->assertEquals('resource2.txt', $files[1]->get_filename());
+
+ // Get forum and count the number of posts on it.
+ $forums = $modinfo->get_instances_of('forum');
+ $forum = reset($forums);
+ $posts = $DB->count_records_sql("
+ SELECT
+ COUNT(1)
+ FROM
+ {forum_posts} fp
+ JOIN {forum_discussions} fd ON fd.id = fp.discussion
+ WHERE
+ fd.forum = ?", array($forum->instance));
+ $this->assertEquals(2, $posts);
+ }
+}
/**
* Version details.
*
- * @package tool
- * @subpackage generator
- * @copyright 2009 Nicolas Connault
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @package tool_generator
+ * @copyright 2013 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2013050100; // The current plugin version (Date: YYYYMMDDXX)
-$plugin->requires = 2013050100; // Requires this Moodle version
-$plugin->component = 'tool_generator'; // Full name of the plugin (used for diagnostics)
-
-$plugin->maturity = MATURITY_ALPHA; // this version's maturity level
+$plugin->version = 2013080700;
+$plugin->requires = 2013080200;
+$plugin->component = 'tool_generator';
navigation_node::override_active_url(new moodle_url('/admin/tool/phpunit/index.php'));
admin_externalpage_setup('toolphpunitwebrunner');
-if (!debugging('', DEBUG_DEVELOPER)) {
+if (!$CFG->debugdeveloper) {
error('Not available on production sites, sorry.');
}
if ($code != 0) {
tool_phpunit_problem('Can not initialize database');
}
- $CFG->debug = 0; // no pesky redirect warning, we really want to redirect
+ set_debugging(DEBUG_NONE, false); // Hack: no redirect warning, we really want to redirect.
redirect(new moodle_url($PAGE->url, array('execute'=>1, 'tespath'=>$testpath, 'testclass'=>$testclass, 'sesskey'=>sesskey())), 'Reloading page');
echo $OUTPUT->footer();
die();
if ($code != 0) {
tool_phpunit_problem('Can not initialize database');
}
- $CFG->debug = 0; // no pesky redirect warning, we really want to redirect
+ set_debugging(DEBUG_NONE, false); // Hack: no redirect warning, we really want to redirect.
redirect(new moodle_url($PAGE->url, array('execute'=>1, 'tespath'=>$testpath, 'testclass'=>$testclass, 'sesskey'=>sesskey())), 'Reloading page');
die();
*/
class tool_uploadcourse_helper {
- /**
- * Remove the restore content from disk and cache.
- *
- * @return void
- */
- public static function clean_restore_content() {
- global $CFG;
-
- // There are some sloppy unclosed file handles in backup/restore code,
- // let's hope somebody unset all controllers before calling this
- // and destroy magic will close all remaining open file handles,
- // otherwise Windows will fail deleting the directory.
- gc_collect_cycles();
-
- if (!empty($CFG->keeptempdirectoriesonbackup)) {
- $cache = cache::make('tool_uploadcourse', 'helper');
- $backupids = (array) $cache->get('backupids');
- foreach ($backupids as $cachekey => $backupid) {
- $cache->delete($cachekey);
- fulldelete("$CFG->tempdir/backup/$backupid/");
- }
- $cache->delete('backupids');
- }
- }
-
/**
* Generate a shortname based on a template.
*
* Get the restore content tempdir.
*
* The tempdir is the sub directory in which the backup has been extracted.
- * This caches the result for better performance.
+ *
+ * This caches the result for better performance, but $CFG->keeptempdirectoriesonbackup
+ * needs to be enabled, otherwise the cache is ignored.
*
* @param string $backupfile path to a backup file.
* @param string $shortname shortname of a course.
$cachekey = null;
if (!empty($backupfile)) {
$backupfile = realpath($backupfile);
+ if (empty($backupfile) || !is_readable($backupfile)) {
+ $errors['cannotreadbackupfile'] = new lang_string('cannotreadbackupfile', 'tool_uploadcourse');
+ return false;
+ }
$cachekey = 'backup_path:' . $backupfile;
} else if (!empty($shortname) || is_numeric($shortname)) {
$cachekey = 'backup_sn:' . $shortname;
return false;
}
- $cache = cache::make('tool_uploadcourse', 'helper');
- if (($backupid = $cache->get($cachekey)) === false) {
- // Use false instead of null because it would consider that the cache
- // key has not been set.
- $backupid = false;
+ // If $CFG->keeptempdirectoriesonbackup is not set to true, any restore happening would
+ // automatically delete the backup directory... causing the cache to return an unexisting directory.
+ $usecache = !empty($CFG->keeptempdirectoriesonbackup);
+ if ($usecache) {
+ $cache = cache::make('tool_uploadcourse', 'helper');
+ }
+
+ // If we don't use the cache, or if we do and not set, or the directory doesn't exist any more.
+ if (!$usecache || (($backupid = $cache->get($cachekey)) === false || !is_dir("$CFG->tempdir/backup/$backupid"))) {
+
+ // Use null instead of false because it would consider that the cache key has not been set.
+ $backupid = null;
+
if (!empty($backupfile)) {
- if (!is_readable($backupfile)) {
- $errors['cannotreadbackupfile'] = new lang_string('cannotreadbackupfile', 'tool_uploadcourse');
- } else {
- // Extracting the backup file.
- $packer = get_file_packer('application/vnd.moodle.backup');
- $backupid = restore_controller::get_tempdir_name(SITEID, $USER->id);
- $path = "$CFG->tempdir/backup/$backupid/";
- $result = $packer->extract_to_pathname($backupfile, $path);
- if (!$result) {
- $errors['invalidbackupfile'] = new lang_string('invalidbackupfile', 'tool_uploadcourse');
- }
+ // Extracting the backup file.
+ $packer = get_file_packer('application/vnd.moodle.backup');
+ $backupid = restore_controller::get_tempdir_name(SITEID, $USER->id);
+ $path = "$CFG->tempdir/backup/$backupid/";
+ $result = $packer->extract_to_pathname($backupfile, $path);
+ if (!$result) {
+ $errors['invalidbackupfile'] = new lang_string('invalidbackupfile', 'tool_uploadcourse');
}
} else if (!empty($shortname) || is_numeric($shortname)) {
// Creating restore from an existing course.
new lang_string('coursetorestorefromdoesnotexist', 'tool_uploadcourse');
}
}
- $cache->set($cachekey, $backupid);
- // Store all the directories to be able to remove them in self::clean_restore_content().
- $backupids = (array) $cache->get('backupids');
- $backupids[$cachekey] = $backupid;
- $cache->set('backupids', $backupids);
+ if ($usecache) {
+ $cache->set($cachekey, $backupid);
+ }
}
+ if ($backupid === null) {
+ $backupid = false;
+ }
return $backupid;
}
$tracker->finish();
$tracker->results($total, $created, $updated, $deleted, $errors);
-
- $this->remove_restore_content();
}
/**
}
$tracker->finish();
- $this->remove_restore_content();
return $preview;
}
- /**
- * Delete the restore object.
- *
- * @return void
- */
- protected function remove_restore_content() {
- tool_uploadcourse_helper::clean_restore_content();
- }
-
/**
* Reset the current process.
*
$mform->disabledIf('options[shortnametemplate]', 'options[mode]', 'eq', tool_uploadcourse_processor::MODE_CREATE_OR_UPDATE);
$mform->disabledIf('options[shortnametemplate]', 'options[mode]', 'eq', tool_uploadcourse_processor::MODE_UPDATE_ONLY);
+ // Restore file is not in the array options on purpose, because formslib can't handle it!
$contextid = $this->_customdata['contextid'];
$mform->addElement('hidden', 'contextid', $contextid);
$mform->setType('contextid', PARAM_INT);
- $mform->addElement('filepicker', 'options[restorefile]', get_string('templatefile', 'tool_uploadcourse'));
- $mform->addHelpButton('options[restorefile]', 'templatefile', 'tool_uploadcourse');
+ $mform->addElement('filepicker', 'restorefile', get_string('templatefile', 'tool_uploadcourse'));
+ $mform->addHelpButton('restorefile', 'templatefile', 'tool_uploadcourse');
$mform->addElement('text', 'options[templatecourse]', get_string('coursetemplatename', 'tool_uploadcourse'));
$mform->setType('options[templatecourse]', PARAM_TEXT);
$mform->closeHeaderBefore('buttonar');
}
- /**
- * Server side validation.
- * @param array $data - form data
- * @param object $files - form files
- * @return array $errors - form errors
- */
- public function validation($data, $files) {
- $errors = parent::validation($data, $files);
- $columns = $this->_customdata['columns'];
- $optype = $data['options']['mode'];
-
- // Look for other required data.
- if ($optype != tool_uploadcourse_processor::MODE_UPDATE_ONLY) {
- if (!in_array('fullname', $columns)) {
- if (isset($errors['mode'])) {
- $errors['mode'] .= ' ';
- }
- $errors['mode'] .= get_string('missingfield', 'error', 'fullname');
- }
- if (!in_array('summary', $columns)) {
- if (isset($errors['mode'])) {
- $errors['mode'] .= ' ';
- }
- $errors['mode'] .= get_string('missingfield', 'error', 'summary');
- }
- }
-
- return $errors;
- }
}
$options = (array) $form2data->options;
$defaults = (array) $form2data->defaults;
+
+ // Restorefile deserves its own logic because formslib does not really appreciate
+ // when the name of a filepicker is an array...
+ $options['restorefile'] = '';
+ if (!empty($form2data->restorefile)) {
+ $options['restorefile'] = $mform2->save_temp_file('restorefile');
+ }
$processor = new tool_uploadcourse_processor($cir, $options, $defaults);
echo $OUTPUT->header();
echo $OUTPUT->continue_button($returnurl);
}
+ // Deleting the file after processing or preview.
+ if (!empty($options['restorefile'])) {
+ @unlink($options['restorefile']);
+ }
+
} else {
$processor = new tool_uploadcourse_processor($cir, $form1data->options, array());
echo $OUTPUT->header();
}
$this->assertTrue($found);
+ // Restoring twice from the same course should work.
+ $data = array('shortname' => 'B1', 'templatecourse' => $c1->shortname, 'summary' => 'B', 'category' => 1,
+ 'fullname' => 'B1');
+ $co = new tool_uploadcourse_course($mode, $updatemode, $data);
+ $this->assertTrue($co->prepare());
+ $co->proceed();
+ $course = $DB->get_record('course', array('shortname' => 'B1'));
+ $modinfo = get_fast_modinfo($course);
+ $found = false;
+ foreach ($modinfo->get_cms() as $cmid => $cm) {
+ if ($cm->modname == 'forum' && $cm->name == $c1f1->name) {
+ $found = true;
+ break;
+ }
+ }
+ $this->assertTrue($found);
+
// Restore the time limit to prevent warning.
set_time_limit(0);
}
}
$this->assertTrue($found);
+ // Restoring twice from the same file should work.
+ $data = array('shortname' => 'B1', 'backupfile' => __DIR__ . '/fixtures/backup.mbz',
+ 'summary' => 'B', 'category' => 1, 'fullname' => 'B1');
+ $co = new tool_uploadcourse_course($mode, $updatemode, $data);
+ $this->assertTrue($co->prepare());
+ $co->proceed();
+ $course = $DB->get_record('course', array('shortname' => 'B1'));
+ $modinfo = get_fast_modinfo($course);
+ $found = false;
+ foreach ($modinfo->get_cms() as $cmid => $cm) {
+ if ($cm->modname == 'glossary' && $cm->name == 'Imported Glossary') {
+ $found = true;
+ } else if ($cm->modname == 'forum' && $cm->name == $c1f1->name) {
+ // We should not find this!
+ $this->assertTrue(false);
+ }
+ }
+ $this->assertTrue($found);
+
// Restore the time limit to prevent warning.
set_time_limit(0);
}
$bc->destroy();
unset($bc); // File logging is a mess, we can only try to rely on gc to close handles.
+ $oldcfg = isset($CFG->keeptempdirectoriesonbackup) ? $CFG->keeptempdirectoriesonbackup : false;
+ $CFG->keeptempdirectoriesonbackup = true;
+
// Checking restore dir.
$dir = tool_uploadcourse_helper::get_restore_content_dir($c1backupfile, null);
$bcinfo = backup_general_helper::get_backup_information($dir);
$this->assertFalse($dir);
$this->assertArrayHasKey('coursetorestorefromdoesnotexist', $errors);
- // Cleaning content directories.
- $oldcfg = isset($CFG->keeptempdirectoriesonbackup) ? $CFG->keeptempdirectoriesonbackup : false;
- $dir = "$CFG->tempdir/backup/$dir";
- $this->assertTrue(file_exists($dir));
-
+ // Trying again without caching. $CFG->keeptempdirectoriesonbackup is required for caching.
$CFG->keeptempdirectoriesonbackup = false;
- tool_uploadcourse_helper::clean_restore_content();
- $this->assertTrue(file_exists($dir));
- $CFG->keeptempdirectoriesonbackup = true;
- tool_uploadcourse_helper::clean_restore_content();
- $this->assertFalse(file_exists($dir));
+ // Checking restore dir.
+ $dir = tool_uploadcourse_helper::get_restore_content_dir($c1backupfile, null);
+ $dir2 = tool_uploadcourse_helper::get_restore_content_dir($c1backupfile, null);
+ $this->assertNotEquals($dir, $dir2);
+
+ // Checking with a shortname.
+ $dir = tool_uploadcourse_helper::get_restore_content_dir(null, $c1->shortname);
+ $dir2 = tool_uploadcourse_helper::get_restore_content_dir(null, $c1->shortname);
+ $this->assertNotEquals($dir, $dir2);
+
+ // Get a course that does not exist.
+ $errors = array();
+ $dir = tool_uploadcourse_helper::get_restore_content_dir(null, 'DoesNotExist', $errors);
+ $this->assertFalse($dir);
+ $this->assertArrayHasKey('coursetorestorefromdoesnotexist', $errors);
+ $dir2 = tool_uploadcourse_helper::get_restore_content_dir(null, 'DoesNotExist', $errors);
+ $this->assertEquals($dir, $dir2);
$CFG->keeptempdirectoriesonbackup = $oldcfg;
$mform->addHelpButton('restrictedusers', 'restrictedusers', 'webservice');
$mform->setType('restrictedusers', PARAM_BOOL);
- //can users download files
+ // Can users download files?
$mform->addElement('advcheckbox', 'downloadfiles', get_string('downloadfiles', 'webservice'));
$mform->setAdvanced('downloadfiles');
$mform->addHelpButton('downloadfiles', 'downloadfiles', 'webservice');
$mform->setType('downloadfiles', PARAM_BOOL);
+ // Can users upload files?
+ $mform->addElement('advcheckbox', 'uploadfiles', get_string('uploadfiles', 'webservice'));
+ $mform->setAdvanced('uploadfiles');
+ $mform->addHelpButton('uploadfiles', 'uploadfiles', 'webservice');
+
/// needed to select automatically the 'No required capability" option
$currentcapabilityexist = false;
if (empty($service->requiredcapability)) {
return $errors;
}
-}
\ No newline at end of file
+}
require_once($CFG->dirroot.'/course/lib.php');
// Ensure errors are well explained
-$CFG->debug = DEBUG_NORMAL;
+set_debugging(DEBUG_DEVELOPER, true);
if (!is_enabled_auth('cas')) {
error_log('[AUTH CAS] '.get_string('pluginnotenabled', 'auth_ldap'));
// Now start the whole NTLM machinery.
if($this->config->ntlmsso_ie_fastpath == AUTH_NTLM_FASTPATH_YESATTEMPT ||
$this->config->ntlmsso_ie_fastpath == AUTH_NTLM_FASTPATH_YESFORM) {
-
- if(check_browser_version('MSIE')) {
+ if (core_useragent::check_ie_version()) {
$sesskey = sesskey();
redirect($CFG->wwwroot.'/auth/ldap/ntlmsso_magic.php?sesskey='.$sesskey);
} else if ($this->config->ntlmsso_ie_fastpath == AUTH_NTLM_FASTPATH_YESFORM) {
require_once($CFG->dirroot.'/course/lib.php');
// Ensure errors are well explained
-$CFG->debug = DEBUG_NORMAL;
+set_debugging(DEBUG_DEVELOPER, true);
if (!is_enabled_auth('ldap')) {
error_log('[AUTH LDAP] '.get_string('pluginnotenabled', 'auth_ldap'));
if ($authplugin->ntlmsso_magic($sesskey) && file_exists($file)) {
if (!empty($authplugin->config->ntlmsso_ie_fastpath)) {
- if (check_browser_version('MSIE')) {
+ if (core_useragent::check_ie_version()) {
// $PAGE->https_required() up above takes care of what $CFG->httpswwwroot should be.
redirect($CFG->httpswwwroot.'/auth/ldap/ntlmsso_finish.php');
}
print_error('unknownbackuptype');
}
+// Backup of large courses requires extra memory. Use the amount configured
+// in admin settings.
+raise_memory_limit(MEMORY_EXTRA);
+
if (!($bc = backup_ui::load_controller($backupid))) {
$bc = new backup_controller($type, $id, backup::FORMAT_MOODLE,
backup::INTERACTIVE_YES, backup::MODE_GENERAL, $USER->id);
// Mark the UI finished.
$rc->finish_ui();
// Execute prechecks
+ $warnings = false;
if (!$rc->execute_precheck()) {
$precheckresults = $rc->get_precheck_results();
- if (is_array($precheckresults) && !empty($precheckresults['errors'])) {
- fulldelete($tempdestination);
-
- echo $OUTPUT->header();
- echo $renderer->precheck_notices($precheckresults);
- echo $OUTPUT->continue_button(new moodle_url('/course/view.php', array('id'=>$course->id)));
- echo $OUTPUT->footer();
- die();
+ if (is_array($precheckresults)) {
+ if (!empty($precheckresults['errors'])) { // If errors are found, terminate the import.
+ fulldelete($tempdestination);
+
+ echo $OUTPUT->header();
+ echo $renderer->precheck_notices($precheckresults);
+ echo $OUTPUT->continue_button(new moodle_url('/course/view.php', array('id'=>$course->id)));
+ echo $OUTPUT->footer();
+ die();
+ }
+ if (!empty($precheckresults['warnings'])) { // If warnings are found, go ahead but display warnings later.
+ $warnings = $precheckresults['warnings'];
+ }
}
- } else {
- if ($restoretarget == backup::TARGET_CURRENT_DELETING || $restoretarget == backup::TARGET_EXISTING_DELETING) {
- restore_dbops::delete_course_content($course->id);
- }
- // Execute the restore
- $rc->execute_plan();
}
+ if ($restoretarget == backup::TARGET_CURRENT_DELETING || $restoretarget == backup::TARGET_EXISTING_DELETING) {
+ restore_dbops::delete_course_content($course->id);
+ }
+ // Execute the restore.
+ $rc->execute_plan();
// Delete the temp directory now
fulldelete($tempdestination);
// Display a notification and a continue button
echo $OUTPUT->header();
- echo $OUTPUT->notification(get_string('importsuccess', 'backup'),'notifysuccess');
+ if ($warnings) {
+ echo $OUTPUT->box_start();
+ echo $OUTPUT->notification(get_string('warning'), 'notifywarning');
+ echo html_writer::start_tag('ul', array('class'=>'list'));
+ foreach ($warnings as $warning) {
+ echo html_writer::tag('li', $warning);
+ }
+ echo html_writer::end_tag('ul');
+ echo $OUTPUT->box_end();
+ }
+ echo $OUTPUT->notification(get_string('importsuccess', 'backup'), 'notifysuccess');
echo $OUTPUT->continue_button(new moodle_url('/course/view.php', array('id'=>$course->id)));
echo $OUTPUT->footer();
*/
abstract class restore_qtype_plugin extends restore_plugin {
+ /*
+ * A simple answer to id cache for a single questions answers.
+ * @var array
+ */
+ private $questionanswercache = array();
+
+ /*
+ * The id of the current question in the questionanswercache.
+ * @var int
+ */
+ private $questionanswercacheid = null;
+
/**
* Add to $paths the restore_path_elements needed
* to handle question_answers for a given question
// The question existed, we need to map the existing question_answers
} else {
- // Look in question_answers by answertext matching
- $sql = 'SELECT id
- FROM {question_answers}
- WHERE question = ?
- AND ' . $DB->sql_compare_text('answer', 255) . ' = ' . $DB->sql_compare_text('?', 255);
- $params = array($newquestionid, $data->answertext);
- $newitemid = $DB->get_field_sql($sql, $params);
-
- // Not able to find the answer, let's try cleaning the answertext
- // of all the question answers in DB as slower fallback. MDL-30018.
- if (!$newitemid) {
+ // Have we cached the current question?
+ if ($this->questionanswercacheid !== $newquestionid) {
+ // The question changed, purge and start again!
+ $this->questionanswercache = array();
$params = array('question' => $newquestionid);
$answers = $DB->get_records('question_answers', $params, '', 'id, answer');
+ $this->questionanswercacheid = $newquestionid;
+ // Cache all cleaned answers for a simple text match.
foreach ($answers as $answer) {
- // Clean in the same way than {@link xml_writer::xml_safe_utf8()}.
+ // MDL-30018: Clean in the same way as {@link xml_writer::xml_safe_utf8()}.
$clean = preg_replace('/[\x-\x8\xb-\xc\xe-\x1f\x7f]/is','', $answer->answer); // Clean CTRL chars.
$clean = preg_replace("/\r\n|\r/", "\n", $clean); // Normalize line ending.
- if ($clean === $data->answertext) {
- $newitemid = $data->id;
- }
+ $this->questionanswercache[$clean] = $answer->id;
}
}
- // If we haven't found the newitemid, something has gone really wrong, question in DB
- // is missing answers, exception
- if (!$newitemid) {
+ if (!isset($this->questionanswercache[$data->answertext])) {
+ // If we haven't found the matching answer, something has gone really wrong, the question in the DB
+ // is missing answers, throw an exception.
$info = new stdClass();
$info->filequestionid = $oldquestionid;
$info->dbquestionid = $newquestionid;
$info->answer = $data->answertext;
throw new restore_step_exception('error_question_answers_missing_in_db', $info);
}
+ $newitemid = $this->questionanswercache[$data->answertext];
}
// Create mapping (we'll use this intensively when restoring question_states. And also answerfeedback files)
$this->set_mapping('question_answer', $oldid, $newitemid);
require_login($course, null, $cm);
require_capability('moodle/restore:restorecourse', $context);
+// Restore of large courses requires extra memory. Use the amount configured
+// in admin settings.
+raise_memory_limit(MEMORY_EXTRA);
+
if ($stage & restore_ui::STAGE_CONFIRM + restore_ui::STAGE_DESTINATION) {
$restore = restore_ui::engage_independent_stage($stage, $contextid);
} else {
global $CFG;
$dfltloglevel = backup::LOG_WARNING; // Default logging level
- if (debugging('', DEBUG_DEVELOPER)) { // Debug developer raises default logging level
+ if ($CFG->debugdeveloper) { // Debug developer raises default logging level
$dfltloglevel = backup::LOG_DEBUG;
}
// Instantiate with debugging enabled and $CFG->backup_error_log_logger_level not set
$CFG->debugdisplay = true;
- $CFG->debug = DEBUG_DEVELOPER;
unset($CFG->backup_error_log_logger_level);
$logger1 = backup_factory::get_logger_chain(backup::INTERACTIVE_YES, backup::EXECUTION_INMEDIATE, 'test');
$this->assertTrue($logger1 instanceof error_log_logger); // 1st logger is error_log_logger
parent::execute();
$this->controller->set_status(backup::STATUS_FINISHED_OK);
- events_trigger('course_restored', (object) array(
- 'courseid' => $this->get_courseid(), // The new course
- 'userid' => $this->get_userid(), // User doing the restore
- 'type' => $this->controller->get_type(), // backup::TYPE_* constant
- 'target' => $this->controller->get_target(), // backup::TARGET_* constant
- 'mode' => $this->controller->get_mode(), // backup::MODE_* constant
- 'operation' => $this->controller->get_operation(), // backup::OPERATION_* constant
- 'samesite' => $this->controller->is_samesite(),
+ // Trigger a course restored event.
+ $event = \core\event\course_restored::create(array(
+ 'objectid' => $this->get_courseid(),
+ 'userid' => $this->get_userid(),
+ 'context' => context_course::instance($this->get_courseid()),
+ 'other' => array('type' => $this->controller->get_type(),
+ 'target' => $this->controller->get_target(),
+ 'mode' => $this->controller->get_mode(),
+ 'operation' => $this->controller->get_operation(),
+ 'samesite' => $this->controller->is_samesite())
));
+ $event->trigger();
}
/**
--- /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 stuff for category enrolment plugin.
+ *
+ * @package core_badges
+ * @copyright 2013 Rajesh Taneja <rajesh@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Event observer for badges.
+ */
+class core_badges_observer {
+ /**
+ * Triggered when 'course_module_completion_updated' event is triggered.
+ *
+ * @param \core\event\course_module_completion_updated $event
+ */
+ public static function course_module_criteria_review(\core\event\course_module_completion_updated $event) {
+ global $DB, $CFG;
+
+ if (!empty($CFG->enablebadges)) {
+ require_once($CFG->dirroot.'/lib/badgeslib.php');
+
+ $eventdata = $event->get_record_snapshot('course_modules_completion', $event->objectid);
+ $userid = $event->other['relateduserid'];
+ $mod = $eventdata->coursemoduleid;
+
+ if ($eventdata->completionstate == COMPLETION_COMPLETE
+ || $eventdata->completionstate == COMPLETION_COMPLETE_PASS
+ || $eventdata->completionstate == COMPLETION_COMPLETE_FAIL) {
+ // Need to take into account that there can be more than one badge with the same activity in its criteria.
+ if ($rs = $DB->get_records('badge_criteria_param', array('name' => 'module_' . $mod, 'value' => $mod))) {
+ foreach ($rs as $r) {
+ $bid = $DB->get_field('badge_criteria', 'badgeid', array('id' => $r->critid), MUST_EXIST);
+ $badge = new badge($bid);
+ if (!$badge->is_active() || $badge->is_issued($userid)) {
+ continue;
+ }
+
+ if ($badge->criteria[BADGE_CRITERIA_TYPE_ACTIVITY]->review($userid)) {
+ $badge->criteria[BADGE_CRITERIA_TYPE_ACTIVITY]->mark_complete($userid);
+
+ if ($badge->criteria[BADGE_CRITERIA_TYPE_OVERALL]->review($userid)) {
+ $badge->criteria[BADGE_CRITERIA_TYPE_OVERALL]->mark_complete($userid);
+ $badge->issue($userid);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Triggered when 'course_completed' event is triggered.
+ *
+ * @param \core\event\course_completed $event
+ */
+ public static function course_criteria_review(\core\event\course_completed $event) {
+ global $DB, $CFG;
+
+ if (!empty($CFG->enablebadges)) {
+ require_once($CFG->dirroot.'/lib/badgeslib.php');
+
+ $eventdata = $event->get_record_snapshot('course_completions', $event->objectid);
+ $userid = $event->other['relateduserid'];
+ $courseid = $event->courseid;
+
+ // Need to take into account that course can be a part of course_completion and courseset_completion criteria.
+ if ($rs = $DB->get_records('badge_criteria_param', array('name' => 'course_' . $courseid, 'value' => $courseid))) {
+ foreach ($rs as $r) {
+ $crit = $DB->get_record('badge_criteria', array('id' => $r->critid), 'badgeid, criteriatype', MUST_EXIST);
+ $badge = new badge($crit->badgeid);
+ if (!$badge->is_active() || $badge->is_issued($userid)) {
+ continue;
+ }
+
+ if ($badge->criteria[$crit->criteriatype]->review($userid)) {
+ $badge->criteria[$crit->criteriatype]->mark_complete($userid);
+
+ if ($badge->criteria[BADGE_CRITERIA_TYPE_OVERALL]->review($userid)) {
+ $badge->criteria[BADGE_CRITERIA_TYPE_OVERALL]->mark_complete($userid);
+ $badge->issue($userid);
+ }
+ }
+ }
+ }
+ }
+ }
+}
defined('MOODLE_INTERNAL') || die();
-require_once(dirname(dirname(dirname(__FILE__))) . '/config.php');
require_once($CFG->libdir . '/badgeslib.php');
require_once($CFG->dirroot . '/user/selector/lib.php');
require_once($CFG->libdir . '/badgeslib.php');
require_once($CFG->libdir . '/tablelib.php');
-require_once($CFG->dirroot . '/user/filters/lib.php');
/**
* Standard HTML output renderer for badges
class core_badgeslib_testcase extends advanced_testcase {
protected $badgeid;
+ protected $course;
+ protected $user;
+ protected $module;
+ protected $coursebadge;
protected function setUp() {
- global $DB;
+ global $DB, $CFG;
$this->resetAfterTest(true);
$user = $this->getDataGenerator()->create_user();
$fordb->status = BADGE_STATUS_INACTIVE;
$this->badgeid = $DB->insert_record('badge', $fordb, true);
+
+ // Create a course with activity and auto completion tracking.
+ $this->course = $this->getDataGenerator()->create_course();
+ $this->user = $this->getDataGenerator()->create_user();
+ $studentrole = $DB->get_record('role', array('shortname' => 'student'));
+ $this->assertNotEmpty($studentrole);
+
+ // Get manual enrolment plugin and enrol user.
+ require_once($CFG->dirroot.'/enrol/manual/locallib.php');
+ $manplugin = enrol_get_plugin('manual');
+ $maninstance = $DB->get_record('enrol', array('courseid' => $this->course->id, 'enrol' => 'manual'), '*', MUST_EXIST);
+ $manplugin->enrol_user($maninstance, $this->user->id, $studentrole->id);
+ $this->assertEquals(1, $DB->count_records('user_enrolments'));
+
+ $completionauto = array('completion' => COMPLETION_TRACKING_AUTOMATIC);
+ $this->module = $this->getDataGenerator()->create_module('forum', array('course' => $this->course->id), $completionauto);
+
+ // Build badge and criteria.
+ $fordb->type = BADGE_TYPE_COURSE;
+ $fordb->courseid = $this->course->id;
+ $fordb->status = BADGE_STATUS_ACTIVE;
+
+ $this->coursebadge = $DB->insert_record('badge', $fordb, true);
}
public function test_create_badge() {
$this->assertEquals(badge_message_from_template($message, $params), $result);
}
+ /**
+ * Test badges observer when course module completion event id fired.
+ */
+ public function test_badges_observer_course_module_criteria_review() {
+ $badge = new badge($this->coursebadge);
+ $this->assertFalse($badge->is_issued($this->user->id));
+
+ $criteria_overall = award_criteria::build(array('criteriatype' => BADGE_CRITERIA_TYPE_OVERALL, 'badgeid' => $badge->id));
+ $criteria_overall->save(array('agg' => BADGE_CRITERIA_AGGREGATION_ANY));
+ $criteria_overall = award_criteria::build(array('criteriatype' => BADGE_CRITERIA_TYPE_ACTIVITY, 'badgeid' => $badge->id));
+ $criteria_overall->save(array('agg' => BADGE_CRITERIA_AGGREGATION_ANY, 'module_'.$this->module->id => $this->module->id));
+
+ // Set completion for forum activity.
+ $c = new completion_info($this->course);
+ $activities = $c->get_activities();
+ $this->assertEquals(1, count($activities));
+ $this->assertTrue(isset($activities[$this->module->cmid]));
+ $this->assertEquals($activities[$this->module->cmid]->name, $this->module->name);
+
+ $current = $c->get_data($activities[$this->module->cmid], false, $this->user->id);
+ $current->completionstate = COMPLETION_COMPLETE;
+ $current->timemodified = time();
+ $c->internal_set_data($activities[$this->module->cmid], $current);
+
+ // Check if badge is awarded.
+ $this->assertDebuggingCalled('Error baking badge image!');
+ $this->assertTrue($badge->is_issued($this->user->id));
+ }
+
+ /**
+ * Test badges observer when course_completed event is fired.
+ */
+ public function test_badges_observer_course_criteria_review() {
+ $badge = new badge($this->coursebadge);
+ $this->assertFalse($badge->is_issued($this->user->id));
+
+ $criteria_overall = award_criteria::build(array('criteriatype' => BADGE_CRITERIA_TYPE_OVERALL, 'badgeid' => $badge->id));
+ $criteria_overall->save(array('agg' => BADGE_CRITERIA_AGGREGATION_ANY));
+ $criteria_overall1 = award_criteria::build(array('criteriatype' => BADGE_CRITERIA_TYPE_COURSE, 'badgeid' => $badge->id));
+ $criteria_overall1->save(array('agg' => BADGE_CRITERIA_AGGREGATION_ANY, 'course_'.$this->course->id => $this->course->id));
+
+ $ccompletion = new completion_completion(array('course' => $this->course->id, 'userid' => $this->user->id));
+
+ // Mark course as complete.
+ $ccompletion->mark_complete();
+
+ // Check if badge is awarded.
+ $this->assertDebuggingCalled('Error baking badge image!');
+ $this->assertTrue($badge->is_issued($this->user->id));
+ }
}
$events = calendar_get_upcoming($courses, $group, $user, $lookahead, $maxevents);
if (!empty($this->instance)) {
- $this->content->text = calendar_get_block_upcoming($events, 'view.php?view=day&course='.$courseshown.'&');
+ $link = 'view.php?view=day&course='.$courseshown.'&';
+ $showcourselink = ($this->page->course->id == SITEID);
+ $this->content->text = calendar_get_block_upcoming($events, $link, $showcourselink);
}
if (empty($this->content->text)) {
'class' => 'block_' . $this->name(). ' block',
'role' => $this->get_aria_role()
);
+ if ($this->hide_header()) {
+ $attributes['class'] .= ' no-header';
+ }
if ($this->instance_can_be_docked() && get_user_preferences('docked_block_instance_'.$this->instance->id, 0)) {
$attributes['class'] .= ' dock_on_load';
}
$content = '';
$moretags = new moodle_url('/tag/coursetags_more.php', array('show'=>$tagtype));
if ($tagtype == 'all') {
- $tags = coursetag_get_tags(0, 0, $this->config->tagtype, $this->config->numberoftags, 'name');
+ $tags = coursetag_get_tags(0, 0, $this->config->tagtype, $this->config->numberoftags);
} else if ($tagtype == 'course') {
- $tags = coursetag_get_tags($this->page->course->id, 0, $this->config->tagtype, $this->config->numberoftags, 'name');
+ $tags = coursetag_get_tags($this->page->course->id, 0, $this->config->tagtype, $this->config->numberoftags);
$moretags->param('courseid', $this->page->course->id);
} else if ($tagtype == 'my') {
- $tags = coursetag_get_tags(0, $USER->id, $this->config->tagtype, $this->config->numberoftags, 'name');
+ $tags = coursetag_get_tags(0, $USER->id, $this->config->tagtype, $this->config->numberoftags);
}
$tagcloud = tag_print_cloud($tags, 150, true);
if (!$tagcloud) {
$this->delete_attachments();
$this->remove_associations();
+ // Get record to pass onto the event.
+ $record = $DB->get_record('post', array('id' => $this->id));
$DB->delete_records('post', array('id' => $this->id));
tag_set('post', $this->id, array());
- add_to_log(SITEID, 'blog', 'delete', 'index.php?userid='. $this->userid, 'deleted blog entry with entry id# '. $this->id);
- events_trigger('blog_entry_deleted', $this);
+ $event = \core\event\blog_entry_deleted::create(array('objectid' => $this->id,
+ 'userid' => $this->userid,
+ 'other' => array("record" => (array)$record)
+ ));
+ $event->add_record_snapshot("post", $record);
+ $event->set_custom_data($this);
+ $event->trigger();
}
/**
* Test various blog related events.
*/
public function test_blog_entry_events() {
- global $USER;
+ global $USER, $DB;
$this->setAdminUser();
$this->resetAfterTest();
$this->assertEquals($blog->id, $event->objectid);
$this->assertEquals($USER->id, $event->userid);
$this->assertEquals("post", $event->objecttable);
+
+ // Delete a blog entry.
+ $record = $DB->get_record('post', array('id' => $blog->id));
+ $blog->delete();
+ $events = $sink->get_events();
+ $event = array_pop($events);
+
+ // Validate event data.
+ $this->assertInstanceOf('\core\event\blog_entry_deleted', $event);
+ $this->assertEquals(context_system::instance()->id, $event->contextid);
+ $this->assertEquals($blog->id, $event->objectid);
+ $this->assertEquals($USER->id, $event->userid);
+ $this->assertEquals("post", $event->objecttable);
+ $this->assertEquals($record, $event->get_record_snapshot("post", $blog->id));
+ $this->assertSame('blog_entry_deleted', $event->get_legacy_eventname());
+
}
}
}
$class = 'cachestore_'.$plugin;
if (!class_exists($class)) {
- $plugins = get_plugin_list_with_file('cachestore', 'lib.php');
+ $plugins = core_component::get_plugin_list_with_file('cachestore', 'lib.php');
if (!array_key_exists($plugin, $plugins)) {
throw new cache_exception('Invalid plugin name specified. The plugin does not exist or is not valid.');
}
}
$class = 'cachelock_'.$plugin;
if (!class_exists($class)) {
- $plugins = get_plugin_list_with_file('cachelock', 'lib.php');
+ $plugins = core_component::get_plugin_list_with_file('cachelock', 'lib.php');
if (!array_key_exists($plugin, $plugins)) {
throw new cache_exception('Invalid lock name specified. The plugin does not exist or is not valid.');
}
if (!array_key_exists($name, $this->configstores)) {
throw new cache_exception('The requested instance does not exist.');
}
- $plugins = get_plugin_list_with_file('cachestore', 'lib.php');
+ $plugins = core_component::get_plugin_list_with_file('cachestore', 'lib.php');
if (!array_key_exists($plugin, $plugins)) {
throw new cache_exception('Invalid plugin name specified. The plugin either does not exist or is not valid.');
}
if (!$coreonly) {
$plugintypes = core_component::get_plugin_types();
foreach ($plugintypes as $type => $location) {
- $plugins = get_plugin_list_with_file($type, 'db/caches.php');
+ $plugins = core_component::get_plugin_list_with_file($type, 'db/caches.php');
foreach ($plugins as $plugin => $filepath) {
$component = clean_param($type.'_'.$plugin, PARAM_COMPONENT); // Standardised plugin name.
$files[$component] = $filepath;
*/
public static function get_store_plugin_summaries() {
$return = array();
- $plugins = get_plugin_list_with_file('cachestore', 'lib.php', true);
+ $plugins = core_component::get_plugin_list_with_file('cachestore', 'lib.php', true);
foreach ($plugins as $plugin => $path) {
$class = 'cachestore_'.$plugin;
$return[$plugin] = array(
$filename = $key.'.cache';
$file = $this->file_path_for_key($key);
$ttl = $this->definition->get_ttl();
+ $maxtime = 0;
if ($ttl) {
$maxtime = cache::now() - $ttl;
}
$strtested = new lang_string('tested', 'cache');
$strnotready = new lang_string('storenotready', 'cache');
-foreach (get_plugin_list_with_file('cachestore', 'lib.php', true) as $plugin => $path) {
+foreach (core_component::get_plugin_list_with_file('cachestore', 'lib.php', true) as $plugin => $path) {
$class = 'cachestore_'.$plugin;
$plugin = get_string('pluginname', 'cachestore_'.$plugin);
* Test the hash_key functionality.
*/
public function test_hash_key() {
- global $CFG;
-
- $currentdebugging = $CFG->debug;
-
- $CFG->debug = E_ALL;
+ $this->resetAfterTest();
+ set_debugging(DEBUG_ALL);
// First with simplekeys
$instance = cache_config_phpunittest::instance(true);
$result = cache_helper::hash_key('test/test', $definition);
$this->assertEquals(sha1($definition->generate_single_key_prefix().'-test/test'), $result);
-
- $CFG->debug = $currentdebugging;
}
}
return $output;
}
+
+/**
+ * Get a HTML link to a course.
+ *
+ * @param int $courseid the course id
+ * @return string a link to the course (as HTML); empty if the course id is invalid
+ */
+function calendar_get_courselink($courseid) {
+
+ if (!$courseid) {
+ return '';
+ }
+
+ calendar_get_course_cached($coursecache, $courseid);
+ $context = context_course::instance($courseid);
+ $fullname = format_string($coursecache[$courseid]->fullname, true, array('context' => $context));
+ $url = new moodle_url('/course/view.php', array('id' => $courseid));
+ $link = html_writer::link($url, $fullname);
+
+ return $link;
+}
+
+
/**
* Add calendar event metadata
*
}
$icon = $OUTPUT->pix_url('icon', $event->modulename) . '';
- $context = context_course::instance($module->course);
- $fullname = format_string($coursecache[$module->course]->fullname, true, array('context' => $context));
-
$event->icon = '<img src="'.$icon.'" alt="'.$eventtype.'" title="'.$modulename.'" class="icon" />';
$event->referer = '<a href="'.$CFG->wwwroot.'/mod/'.$event->modulename.'/view.php?id='.$module->id.'">'.$event->name.'</a>';
- $event->courselink = '<a href="'.$CFG->wwwroot.'/course/view.php?id='.$module->course.'">'.$fullname.'</a>';
+ $event->courselink = calendar_get_courselink($module->course);
$event->cmid = $module->id;
-
} else if($event->courseid == SITEID) { // Site event
$event->icon = '<img src="'.$OUTPUT->pix_url('i/siteevent') . '" alt="'.get_string('globalevent', 'calendar').'" class="icon" />';
$event->cssclass = 'calendar_event_global';
} else if($event->courseid != 0 && $event->courseid != SITEID && $event->groupid == 0) { // Course event
- calendar_get_course_cached($coursecache, $event->courseid);
-
- $context = context_course::instance($event->courseid);
- $fullname = format_string($coursecache[$event->courseid]->fullname, true, array('context' => $context));
-
$event->icon = '<img src="'.$OUTPUT->pix_url('i/courseevent') . '" alt="'.get_string('courseevent', 'calendar').'" class="icon" />';
- $event->courselink = '<a href="'.$CFG->wwwroot.'/course/view.php?id='.$event->courseid.'">'.$fullname.'</a>';
+ $event->courselink = calendar_get_courselink($event->courseid);
$event->cssclass = 'calendar_event_course';
} else if ($event->groupid) { // Group event
$event->icon = '<img src="'.$OUTPUT->pix_url('i/groupevent') . '" alt="'.get_string('groupevent', 'calendar').'" class="icon" />';
+ $event->courselink = calendar_get_courselink($event->courseid);
$event->cssclass = 'calendar_event_group';
} else if($event->userid) { // User event
$event->icon = '<img src="'.$OUTPUT->pix_url('i/userevent') . '" alt="'.get_string('userevent', 'calendar').'" class="icon" />';
*
* @param array $events list of events
* @param moodle_url|string $linkhref link to event referer
+ * @param boolean $showcourselink whether links to courses should be shown
* @return string|null $content html block content
*/
-function calendar_get_block_upcoming($events, $linkhref = NULL) {
+function calendar_get_block_upcoming($events, $linkhref = NULL, $showcourselink = false) {
$content = '';
$lines = count($events);
if (!$lines) {
}
}
$events[$i]->time = str_replace('»', '<br />»', $events[$i]->time);
+ if ($showcourselink && !empty($events[$i]->courselink)) {
+ $content .= html_writer::div($events[$i]->courselink, 'course');
+ }
$content .= '<div class="date">'.$events[$i]->time.'</div></div>';
if ($i < $lines - 1) $content .= '<hr />';
}
$cohort->id = $DB->insert_record('cohort', $cohort);
- events_trigger('cohort_added', $cohort);
+ $event = \core\event\cohort_created::create(array(
+ 'context' => context::instance_by_id($cohort->contextid),
+ 'objectid' => $cohort->id,
+ ));
+ $event->add_record_snapshot('cohort', $cohort);
+ $event->trigger();
return $cohort->id;
}
$cohort->timemodified = time();
$DB->update_record('cohort', $cohort);
- events_trigger('cohort_updated', $cohort);
+ $event = \core\event\cohort_updated::create(array(
+ 'context' => context::instance_by_id($cohort->contextid),
+ 'objectid' => $cohort->id,
+ ));
+ $event->add_record_snapshot('cohort', $cohort);
+ $event->trigger();
}
/**
$DB->delete_records('cohort_members', array('cohortid'=>$cohort->id));
$DB->delete_records('cohort', array('id'=>$cohort->id));
- events_trigger('cohort_deleted', $cohort);
+ $event = \core\event\cohort_deleted::create(array(
+ 'context' => context::instance_by_id($cohort->contextid),
+ 'objectid' => $cohort->id,
+ ));
+ $event->add_record_snapshot('cohort', $cohort);
+ $event->trigger();
}
/**
$record->timeadded = time();
$DB->insert_record('cohort_members', $record);
- events_trigger('cohort_member_added', (object)array('cohortid'=>$cohortid, 'userid'=>$userid));
+ $cohort = $DB->get_record('cohort', array('id' => $cohortid), '*', MUST_EXIST);
+
+ $event = \core\event\cohort_member_added::create(array(
+ 'context' => context::instance_by_id($cohort->contextid),
+ 'objectid' => $cohortid,
+ 'relateduserid' => $userid,
+ ));
+ $event->add_record_snapshot('cohort', $cohort);
+ $event->trigger();
}
/**
global $DB;
$DB->delete_records('cohort_members', array('cohortid'=>$cohortid, 'userid'=>$userid));
- events_trigger('cohort_member_removed', (object)array('cohortid'=>$cohortid, 'userid'=>$userid));
+ $cohort = $DB->get_record('cohort', array('id' => $cohortid), '*', MUST_EXIST);
+
+ $event = \core\event\cohort_member_removed::create(array(
+ 'context' => context::instance_by_id($cohort->contextid),
+ 'objectid' => $cohortid,
+ 'relateduserid' => $userid,
+ ));
+ $event->add_record_snapshot('cohort', $cohort);
+ $event->trigger();
}
/**
$this->assertNotEmpty($newcohort->timecreated);
$this->assertSame($newcohort->component, '');
$this->assertSame($newcohort->timecreated, $newcohort->timemodified);
+ }
+
+ public function test_cohort_add_cohort_missing_name() {
+ $cohort = new stdClass();
+ $cohort->contextid = context_system::instance()->id;
+ $cohort->name = null;
+ $cohort->idnumber = 'testid';
+ $cohort->description = 'test cohort desc';
+ $cohort->descriptionformat = FORMAT_HTML;
+
+ $this->setExpectedException('coding_exception', 'Missing cohort name in cohort_add_cohort().');
+ cohort_add_cohort($cohort);
+ }
+
+ public function test_cohort_add_cohort_event() {
+ $this->resetAfterTest();
+
+ // Setup cohort data structure.
+ $cohort = new stdClass();
+ $cohort->contextid = context_system::instance()->id;
+ $cohort->name = 'test cohort';
+ $cohort->idnumber = 'testid';
+ $cohort->description = 'test cohort desc';
+ $cohort->descriptionformat = FORMAT_HTML;
- try {
- $cohort = new stdClass();
- $cohort->contextid = context_system::instance()->id;
- $cohort->name = null;
- $cohort->idnumber = 'testid';
- $cohort->description = 'test cohort desc';
- $cohort->descriptionformat = FORMAT_HTML;
- cohort_add_cohort($cohort);
-
- $this->fail('Exception expected when trying to add cohort without name');
- } catch (Exception $e) {
- $this->assertInstanceOf('coding_exception', $e);
- }
+ // Catch Events.
+ $sink = $this->redirectEvents();
+
+ // Perform the add operation.
+ $id = cohort_add_cohort($cohort);
+
+ // Capture the event.
+ $events = $sink->get_events();
+ $sink->close();
+
+ // Validate the event.
+ $this->assertCount(1, $events);
+ $event = $events[0];
+ $this->assertInstanceOf('\core\event\cohort_created', $event);
+ $this->assertEquals('cohort', $event->objecttable);
+ $this->assertEquals($id, $event->objectid);
+ $this->assertEquals($cohort->contextid, $event->contextid);
+ $this->assertEquals($cohort, $event->get_record_snapshot('cohort', $id));
+ $this->assertEventLegacyData($cohort, $event);
}
public function test_cohort_update_cohort() {
$this->assertLessThanOrEqual(time(), $newcohort->timemodified);
}
+ public function test_cohort_update_cohort_event() {
+ global $DB;
+
+ $this->resetAfterTest();
+
+ // Setup the cohort data structure.
+ $cohort = new stdClass();
+ $cohort->contextid = context_system::instance()->id;
+ $cohort->name = 'test cohort';
+ $cohort->idnumber = 'testid';
+ $cohort->description = 'test cohort desc';
+ $cohort->descriptionformat = FORMAT_HTML;
+ $id = cohort_add_cohort($cohort);
+ $this->assertNotEmpty($id);
+
+ $cohort->name = 'test cohort 2';
+
+ // Catch Events.
+ $sink = $this->redirectEvents();
+
+ // Peform the update.
+ cohort_update_cohort($cohort);
+
+ $events = $sink->get_events();
+ $sink->close();
+
+ // Validate the event.
+ $this->assertCount(1, $events);
+ $event = $events[0];
+ $updatedcohort = $DB->get_record('cohort', array('id'=>$id));
+ $this->assertInstanceOf('\core\event\cohort_updated', $event);
+ $this->assertEquals('cohort', $event->objecttable);
+ $this->assertEquals($updatedcohort->id, $event->objectid);
+ $this->assertEquals($updatedcohort->contextid, $event->contextid);
+ $this->assertEquals($cohort, $event->get_record_snapshot('cohort', $id));
+ $this->assertEventLegacyData($cohort, $event);
+ }
+
public function test_cohort_delete_cohort() {
global $DB;
$this->assertFalse($DB->record_exists('cohort', array('id'=>$cohort->id)));
}
+ public function test_cohort_delete_cohort_event() {
+
+ $this->resetAfterTest();
+
+ $cohort = $this->getDataGenerator()->create_cohort();
+
+ // Capture the events.
+ $sink = $this->redirectEvents();
+
+ // Perform the delete.
+ cohort_delete_cohort($cohort);
+
+ $events = $sink->get_events();
+ $sink->close();
+
+ // Validate the event structure.
+ $this->assertCount(1, $events);
+ $event = $events[0];
+ $this->assertInstanceOf('\core\event\cohort_deleted', $event);
+ $this->assertEquals('cohort', $event->objecttable);
+ $this->assertEquals($cohort->id, $event->objectid);
+ $this->assertEquals($cohort, $event->get_record_snapshot('cohort', $cohort->id));
+ $this->assertEventLegacyData($cohort, $event);
+ }
+
public function test_cohort_delete_category() {
global $DB;
$this->assertTrue($DB->record_exists('cohort_members', array('cohortid'=>$cohort->id, 'userid'=>$user->id)));
}
+ public function test_cohort_add_member_event() {
+ global $USER;
+ $this->resetAfterTest();
+
+ // Setup the data.
+ $cohort = $this->getDataGenerator()->create_cohort();
+ $user = $this->getDataGenerator()->create_user();
+
+ // Capture the events.
+ $sink = $this->redirectEvents();
+
+ // Peform the add member operation.
+ cohort_add_member($cohort->id, $user->id);
+
+ $events = $sink->get_events();
+ $sink->close();
+
+ // Validate the event.
+ $this->assertCount(1, $events);
+ $event = $events[0];
+ $this->assertInstanceOf('\core\event\cohort_member_added', $event);
+ $this->assertEquals('cohort', $event->objecttable);
+ $this->assertEquals($cohort->id, $event->objectid);
+ $this->assertEquals($user->id, $event->relateduserid);
+ $this->assertEquals($USER->id, $event->userid);
+ $this->assertEventLegacyData((object) array('cohortid' => $cohort->id, 'userid' => $user->id), $event);
+ }
+
public function test_cohort_remove_member() {
global $DB;
$this->assertFalse($DB->record_exists('cohort_members', array('cohortid'=>$cohort->id, 'userid'=>$user->id)));
}
+ public function test_cohort_remove_member_event() {
+ global $USER;
+ $this->resetAfterTest();
+
+ // Setup the data.
+ $cohort = $this->getDataGenerator()->create_cohort();
+ $user = $this->getDataGenerator()->create_user();
+ cohort_add_member($cohort->id, $user->id);
+
+ // Capture the events.
+ $sink = $this->redirectEvents();
+
+ // Peform the remove operation.
+ cohort_remove_member($cohort->id, $user->id);
+ $events = $sink->get_events();
+ $sink->close();
+
+ // Validate the event.
+ $this->assertCount(1, $events);
+ $event = $events[0];
+ $this->assertInstanceOf('\core\event\cohort_member_removed', $event);
+ $this->assertEquals('cohort', $event->objecttable);
+ $this->assertEquals($cohort->id, $event->objectid);
+ $this->assertEquals($user->id, $event->relateduserid);
+ $this->assertEquals($USER->id, $event->userid);
+ $this->assertEventLegacyData((object) array('cohortid' => $cohort->id, 'userid' => $user->id), $event);
+ }
+
public function test_cohort_is_member() {
global $DB;
* @return void
*/
public function mark_complete($timecomplete = null) {
+ global $USER;
- // Never change a completion time
+ // Never change a completion time.
if ($this->timecompleted) {
return;
}
- // Use current time if nothing supplied
+ // Use current time if nothing supplied.
if (!$timecomplete) {
$timecomplete = time();
}
- // Set time complete
+ // Set time complete.
$this->timecompleted = $timecomplete;
- // Save record
+ // Save record.
if ($result = $this->_save()) {
- events_trigger('course_completed', $this->get_record_data());
+ $data = $this->get_record_data();
+ $event = \core\event\course_completed::create(
+ array(
+ 'objectid' => $data->id,
+ 'userid' => $USER->id,
+ 'context' => context_course::instance($data->course),
+ 'courseid' => $data->course,
+ 'other' => array('relateduserid' => $data->userid)
+ )
+ );
+ $event->add_record_snapshot('course_completions', $data);
+ $event->trigger();
}
return $result;
// Locking resolves race conditions and is strongly recommended for production servers.
// $CFG->preventfilelocking = false;
//
-// If $CFG->langstringcache is enabled (which should always be in production
-// environment), Moodle keeps aggregated strings in its own internal format
-// optimised for performance. By default, this on-disk cache is created in
-// $CFG->cachedir/lang. In cluster environment, you may wish to specify
-// an alternative location of this cache so that each web server in the cluster
-// uses its own local cache and does not need to access the shared dataroot.
-// Make sure that the web server process has write permission to this location
-// and that it has permission to remove the folder, too (so that the cache can
-// be pruned).
-//
-// $CFG->langcacheroot = '/var/www/moodle/htdocs/altcache/lang';
-//
-// If $CFG->langcache is enabled (which should always be in production
-// environment), Moodle stores the list of available languages in a cache file.
-// By default, the file $CFG->dataroot/languages is used. You may wish to
-// specify an alternative location of this cache file.
-//
-// $CFG->langmenucachefile = '/var/www/moodle/htdocs/altcache/languages';
-//
// Site default language can be set via standard administration interface. If you
// want to have initial error messages for eventual database connection problems
// localized too, you have to set your language code here.
// $CFG->debugusers = '2';
//
// Prevent theme caching
-// $CFG->themerev = -1; // NOT FOR PRODUCTION SERVERS!
+// $CFG->themedesignermode = true; // NOT FOR PRODUCTION SERVERS!
//
// Prevent JS caching
-// $CFG->jsrev = -1; // NOT FOR PRODUCTION SERVERS!
+// $CFG->cachejs = false; // NOT FOR PRODUCTION SERVERS!
//
-// Prevent core_string_manager on-disk cache
+// Prevent core_string_manager application caching
// $CFG->langstringcache = false; // NOT FOR PRODUCTION SERVERS!
//
// When working with production data on test servers, no emails or other messages
print_error('confirmsesskeybad', 'error');
}
- // OK checks done, delete the course now.
-
- add_to_log(SITEID, "course", "delete", "view.php?id=$course->id", "$course->fullname (ID $course->id)");
-
$strdeletingcourse = get_string("deletingcourse", "", $courseshortname);
$PAGE->navbar->add($strdeletingcourse);
$context = context_course::instance($course->id);
require_capability('moodle/course:update', $context);
-// get section_info object with all availability options
+// Get section_info object with all availability options.
$sectioninfo = get_fast_modinfo($course)->get_section_info($sectionnum);
$editoroptions = array('context'=>$context ,'maxfiles' => EDITOR_UNLIMITED_FILES, 'maxbytes'=>$CFG->maxbytes, 'trusttext'=>false, 'noclean'=>true);
$mform->set_data(convert_to_array($sectioninfo));
if ($mform->is_cancelled()){
- // form cancelled, return to course
+ // Form cancelled, return to course.
redirect(course_get_url($course, $section, array('sr' => $sectionreturn)));
} else if ($data = $mform->get_data()) {
- // data submitted and validated, update and return to course
+ // Data submitted and validated, update and return to course.
$DB->update_record('course_sections', $data);
rebuild_course_cache($course->id, true);
if (isset($data->section)) {
- // usually edit form does not change relative section number but just in case
+ // Usually edit form does not change relative section number but just in case.
$sectionnum = $data->section;
}
if (!empty($CFG->enableavailability)) {
- // Update grade and completion conditions
+ // Update grade and completion conditions.
$sectioninfo = get_fast_modinfo($course)->get_section_info($sectionnum);
condition_info_section::update_section_from_form($sectioninfo, $data);
rebuild_course_cache($course->id, true);
}
course_get_format($course->id)->update_section_format_options($data);
- add_to_log($course->id, "course", "editsection", "editsection.php?id=$id", "$sectionnum");
+ // Set section info, as this might not be present in form_data.
+ if (!isset($data->section)) {
+ $data->section = $sectionnum;
+ }
+ // Trigger an event for course section update.
+ $event = \core\event\course_section_updated::create(
+ array(
+ 'objectid' => $data->id,
+ 'courseid' => $course->id,
+ 'context' => $context,
+ 'other' => array('sectionnum' => $data->section)
+ )
+ );
+ $event->trigger();
+
$PAGE->navigation->clear_cache();
redirect(course_get_url($course, $section, array('sr' => $sectionreturn)));
}
-// the edit form is displayed for the first time or there was a validation
-// error on the previous step. Display the edit form:
+// The edit form is displayed for the first time or if there was validation error on the previous step.
$sectionname = get_section_name($course, $sectionnum);
$stredit = get_string('edita', '', " $sectionname");
$strsummaryof = get_string('summaryof', '', " $sectionname");
// When on a section page, we only display the general section title, if title is not the default one
$hasnamesecpg = ($onsectionpage && ($section->section == 0 && !is_null($section->name)));
+ $classes = ' accesshide';
if ($hasnamenotsecpg || $hasnamesecpg) {
- $o.= $this->output->heading($this->section_title($section, $course), 3, 'sectionname');
+ $classes = '';
}
+ $o.= $this->output->heading($this->section_title($section, $course), 3, 'sectionname' . $classes);
$o.= html_writer::start_tag('div', array('class' => 'summary'));
$o.= $this->format_summary_text($section);
require_once($CFG->libdir.'/completionlib.php');
require_once($CFG->libdir.'/filelib.php');
-require_once($CFG->dirroot.'/course/dnduploadlib.php');
require_once($CFG->dirroot.'/course/format/lib.php');
define('COURSE_MAX_LOGS_PER_PAGE', 1000); // records
function make_categories_options() {
global $CFG;
require_once($CFG->libdir. '/coursecatlib.php');
- $cats = coursecat::make_categories_list();
+ $cats = coursecat::make_categories_list('', 0, ' / ');
foreach ($cats as $key => $value) {
- $cats[$key] = str_repeat(' ', coursecat::get($key)->depth - 1). $value;
+ // Prefix the value with the number of spaces equal to category depth (number of separators in the value).
+ $cats[$key] = str_repeat(' ', substr_count($value, ' / ')). $value;
}
return $cats;
}
*
* Updates both tables {course_sections} and {course_modules}
*
+ * Note: This function does not use modinfo PROVIDED that the section you are
+ * adding the module to already exists. If the section does not exist, it will
+ * build modinfo if necessary and create the section.
+ *
* @param int|stdClass $courseorid course id or course object
* @param int $cmid id of the module already existing in course_modules table
* @param int $sectionnum relative number of the section (field course_sections.section)
} else {
$courseid = $courseorid;
}
- course_create_sections_if_missing($courseorid, $sectionnum);
// Do not try to use modinfo here, there is no guarantee it is valid!
- $section = $DB->get_record('course_sections', array('course'=>$courseid, 'section'=>$sectionnum), '*', MUST_EXIST);
+ $section = $DB->get_record('course_sections',
+ array('course' => $courseid, 'section' => $sectionnum), '*', IGNORE_MISSING);
+ if (!$section) {
+ // This function call requires modinfo.
+ course_create_sections_if_missing($courseorid, $sectionnum);
+ $section = $DB->get_record('course_sections',
+ array('course' => $courseid, 'section' => $sectionnum), '*', MUST_EXIST);
+ }
+
$modarray = explode(",", trim($section->sequence));
if (empty($section->sequence)) {
$newsequence = "$cmid";
array('class' => 'editing_'. $actionname, 'data-action' => $actionname, 'data-nextgroupmode' => $nextgroupmode)
);
} else {
- $actions[$actionname] = new pix_icon($groupimage, $forcedgrouptitle, 'moodle', array('title' => '', 'class' => 'iconsmall'));
+ $actions[$actionname] = new pix_icon($groupimage, $forcedgrouptitle, 'moodle', array('class' => 'iconsmall'));
}
}
* @return bool success
*/
function move_courses($courseids, $categoryid) {
- global $CFG, $DB, $OUTPUT;
+ global $DB;
if (empty($courseids)) {
- // nothing to do
+ // Nothing to do.
return;
}
- if (!$category = $DB->get_record('course_categories', array('id'=>$categoryid))) {
+ if (!$category = $DB->get_record('course_categories', array('id' => $categoryid))) {
return false;
}
$i = 1;
foreach ($courseids as $courseid) {
- if ($course = $DB->get_record('course', array('id'=>$courseid), 'id, category')) {
+ if ($dbcourse = $DB->get_record('course', array('id' => $courseid))) {
$course = new stdClass();
$course->id = $courseid;
$course->category = $category->id;
$course->sortorder = $category->sortorder + MAX_COURSES_IN_CATEGORY - $i++;
if ($category->visible == 0) {
- // hide the course when moving into hidden category,
- // do not update the visibleold flag - we want to get to previous state if somebody unhides the category
+ // Hide the course when moving into hidden category, do not update the visibleold flag - we want to get
+ // to previous state if somebody unhides the category.
$course->visible = 0;
}
$DB->update_record('course', $course);
- add_to_log($course->id, "course", "move", "edit.php?id=$course->id", $course->id);
- $context = context_course::instance($course->id);
+ // Store the context.
+ $context = context_course::instance($course->id);
+
+ // Update the course object we are passing to the event.
+ $dbcourse->category = $course->category;
+ $dbcourse->sortorder = $course->sortorder;
+
+ // Trigger a course updated event.
+ $event = \core\event\course_updated::create(array(
+ 'objectid' => $course->id,
+ 'context' => $context,
+ 'other' => array('shortname' => $dbcourse->shortname,
+ 'fullname' => $dbcourse->fullname)
+ ));
+ $event->add_record_snapshot('course', $dbcourse);
+ $event->set_legacy_logdata(array($course->id, 'course', 'move', 'edit.php?id=' . $course->id, $course->id));
+ $event->trigger();
+
$context->update_moved($newparent);
}
}
* @return object new course instance
*/
function create_course($data, $editoroptions = NULL) {
- global $CFG, $DB;
+ global $DB;
//check the categoryid - must be given for all new courses
$category = $DB->get_record('course_categories', array('id'=>$data->category), '*', MUST_EXIST);
// set up enrolments
enrol_course_updated(true, $course, $data);
- add_to_log(SITEID, 'course', 'new', 'view.php?id='.$course->id, $data->fullname.' (ID '.$course->id.')');
-
- // Trigger events
- events_trigger('course_created', $course);
+ // Trigger a course created event.
+ $event = \core\event\course_created::create(array(
+ 'objectid' => $course->id,
+ 'context' => context_course::instance($course->id),
+ 'other' => array('shortname' => $course->shortname,
+ 'fullname' => $course->fullname)
+ ));
+ $event->add_record_snapshot('course', $course);
+ $event->trigger();
return $course;
}
* @return void
*/
function update_course($data, $editoroptions = NULL) {
- global $CFG, $DB;
+ global $DB;
$data->timemodified = time();
// update enrol settings
enrol_course_updated(false, $course, $data);
- add_to_log($course->id, "course", "update", "edit.php?id=$course->id", $course->id);
-
- // Trigger events
- events_trigger('course_updated', $course);
+ // Trigger a course updated event.
+ $event = \core\event\course_updated::create(array(
+ 'objectid' => $course->id,
+ 'context' => $context,
+ 'other' => array('shortname' => $course->shortname,
+ 'fullname' => $course->fullname)
+ ));
+ $event->add_record_snapshot('course', $course);
+ $event->set_legacy_logdata(array($course->id, 'course', 'update', 'edit.php?id=' . $course->id, $course->id));
+ $event->trigger();
if ($oldcourse->format !== $course->format) {
// Remove all options stored for the previous format
* @return bool
*/
function include_course_ajax($course, $usedmodules = array(), $enabledmodules = null, $config = null) {
- global $PAGE, $SITE;
+ global $CFG, $PAGE, $SITE;
// Ensure that ajax should be included
if (!course_ajax_enabled($course)) {
'markedthistopic',
'move',
'movesection',
+ 'movecontent',
+ 'tocontent',
+ 'emptydragdropregion'
), 'moodle');
// Include format-specific strings
}
// Load drag and drop upload AJAX.
+ require_once($CFG->dirroot.'/course/dnduploadlib.php');
dndupload_add_to_course($course, $enabledmodules);
return true;
<?php
-// Allows a teacher/admin to login as another user (in stealth mode)
+// Allows a teacher/admin to login as another user (in stealth mode).
require_once('../config.php');
require_once('lib.php');
$url = new moodle_url('/course/loginas.php', array('id'=>$id));
$PAGE->set_url($url);
-/// Reset user back to their real self if needed, for security reasons you need to log out and log in again
+// Reset user back to their real self if needed, for security reasons you need to log out and log in again.
if (session_is_loggedinas()) {
require_sesskey();
require_logout();
redirect(get_login_url());
}
-///-------------------------------------
-/// We are trying to log in as this user in the first place
-
-$userid = required_param('user', PARAM_INT); // login as this user
+// Try log in as this user.
+$userid = required_param('user', PARAM_INT);
require_sesskey();
$course = $DB->get_record('course', array('id'=>$id), '*', MUST_EXIST);
-/// User must be logged in
+// User must be logged in.
$systemcontext = context_system::instance();
$coursecontext = context_course::instance($course->id);
$context = $coursecontext;
}
-/// Login as this user and return to course home page.
-$oldfullname = fullname($USER, true);
+// Login as this user and return to course home page.
session_loginas($userid, $context);
$newfullname = fullname($USER, true);
-add_to_log($course->id, "course", "loginas", "../user/view.php?id=$course->id&user=$userid", "$oldfullname -> $newfullname");
-
$strloginas = get_string('loginas');
$strloggedinas = get_string('loggedinas', '', $newfullname);
$params = array('id' => $course->id, 'visible' => $visible, 'visibleold' => $visible, 'timemodified' => time());
$DB->update_record('course', $params);
cache_helper::purge_by_event('changesincourse');
- add_to_log($course->id, "course", ($visible ? 'show' : 'hide'), "edit.php?id=$course->id", $course->id);
+
+ // Update the course object we pass to the event class.
+ $course->visible = $params['visible'];
+ $course->visibleold = $params['visibleold'];
+ $course->timemodified = $params['timemodified'];
+
+ // Trigger a course updated event.
+ $event = \core\event\course_updated::create(array(
+ 'objectid' => $course->id,
+ 'context' => $coursecontext,
+ 'other' => array('shortname' => $course->shortname,
+ 'fullname' => $course->fullname)
+ ));
+ $event->add_record_snapshot('course', $course);
+ $event->set_legacy_logdata(array($course->id, 'course', ($visible ? 'show' : 'hide'), 'edit.php?id=' . $course->id, $course->id));
+ $event->trigger();
}
if ((!empty($moveup) or !empty($movedown)) && confirm_sesskey()) {
$DB->set_field('course', 'sortorder', $swapcourse->sortorder, array('id' => $movecourse->id));
$DB->set_field('course', 'sortorder', $movecourse->sortorder, array('id' => $swapcourse->id));
cache_helper::purge_by_event('changesincourse');
- add_to_log($movecourse->id, "course", "move", "edit.php?id=$movecourse->id", $movecourse->id);
+
+ // Update $movecourse's sortorder.
+ $movecourse->sortorder = $swapcourse->sortorder;
+
+ // Trigger a course updated event.
+ $event = \core\event\course_updated::create(array(
+ 'objectid' => $movecourse->id,
+ 'context' => context_course::instance($movecourse->id),
+ 'other' => array('shortname' => $movecourse->shortname,
+ 'fullname' => $movecourse->fullname)
+ ));
+ $event->add_record_snapshot('course', $movecourse);
+ $event->set_legacy_logdata(array($movecourse->id, 'course', 'move', 'edit.php?id=' . $movecourse->id, $movecourse->id));
+ $event->trigger();
}
}
$label = is_null($customlabel) ? get_string('moduleintro') : $customlabel;
$mform->addElement('editor', 'introeditor', $label, array('rows' => 10), array('maxfiles' => EDITOR_UNLIMITED_FILES,
- 'noclean' => true, 'context' => $this->context, 'collapsed' => true));
+ 'noclean' => true, 'context' => $this->context));
$mform->setType('introeditor', PARAM_RAW); // no XSS prevention here, users must be trusted
if ($required) {
$mform->addRule('introeditor', get_string('required'), 'required', null, 'client');
$this->assertEquals(range(0, $course->numsections + 1), $sectionscreated);
}
+ public function test_course_add_cm_to_section() {
+ global $DB;
+ $this->resetAfterTest(true);
+
+ // Create course with 1 section.
+ $course = $this->getDataGenerator()->create_course(
+ array('shortname' => 'GrowingCourse',
+ 'fullname' => 'Growing Course',
+ 'numsections' => 1),
+ array('createsections' => true));
+
+ // Trash modinfo.
+ rebuild_course_cache($course->id, true);
+
+ // Create some cms for testing.
+ $cmids = array();
+ for ($i=0; $i<4; $i++) {
+ $cmids[$i] = $DB->insert_record('course_modules', array('course' => $course->id));
+ }
+
+ // Add it to section that exists.
+ course_add_cm_to_section($course, $cmids[0], 1);
+
+ // Check it got added to sequence.
+ $sequence = $DB->get_field('course_sections', 'sequence', array('course' => $course->id, 'section' => 1));
+ $this->assertEquals($cmids[0], $sequence);
+
+ // Add a second, this time using courseid variant of parameters.
+ course_add_cm_to_section($course->id, $cmids[1], 1);
+ $sequence = $DB->get_field('course_sections', 'sequence', array('course' => $course->id, 'section' => 1));
+ $this->assertEquals($cmids[0] . ',' . $cmids[1], $sequence);
+
+ // Check modinfo was not rebuilt (important for performance if calling
+ // repeatedly).
+ $this->assertNull($DB->get_field('course', 'modinfo', array('id' => $course->id)));
+
+ // Add one to section that doesn't exist (this might rebuild modinfo).
+ course_add_cm_to_section($course, $cmids[2], 2);
+ $this->assertEquals(3, $DB->count_records('course_sections', array('course' => $course->id)));
+ $sequence = $DB->get_field('course_sections', 'sequence', array('course' => $course->id, 'section' => 2));
+ $this->assertEquals($cmids[2], $sequence);
+
+ // Add using the 'before' option.
+ course_add_cm_to_section($course, $cmids[3], 2, $cmids[2]);
+ $this->assertEquals(3, $DB->count_records('course_sections', array('course' => $course->id)));
+ $sequence = $DB->get_field('course_sections', 'sequence', array('course' => $course->id, 'section' => 2));
+ $this->assertEquals($cmids[3] . ',' . $cmids[2], $sequence);
+ }
+
public function test_reorder_sections() {
global $DB;
$this->resetAfterTest(true);
$eventcount = $DB->count_records('event', array('instance' => $assign->id, 'modulename' => 'assign'));
$this->assertEmpty($eventcount);
}
+
+ /**
+ * Test that triggering a course_created event works as expected.
+ */
+ public function test_course_created_event() {
+ $this->resetAfterTest();
+
+ // Catch the events.
+ $sink = $this->redirectEvents();
+
+ // Create the course.
+ $course = $this->getDataGenerator()->create_course();
+
+ // Capture the event.
+ $events = $sink->get_events();
+ $sink->close();
+
+ // Validate the event.
+ $event = $events[0];
+ $this->assertInstanceOf('\core\event\course_created', $event);
+ $this->assertEquals('course', $event->objecttable);
+ $this->assertEquals($course->id, $event->objectid);
+ $this->assertEquals(context_course::instance($course->id)->id, $event->contextid);
+ $this->assertEquals($course, $event->get_record_snapshot('course', $course->id));
+ $this->assertEquals('course_created', $event->get_legacy_eventname());
+ $this->assertEventLegacyData($course, $event);
+ $expectedlog = array(SITEID, 'course', 'new', 'view.php?id=' . $course->id, $course->fullname . ' (ID ' . $course->id . ')');
+ $this->assertEventLegacyLogData($expectedlog, $event);
+ }
+
+ /**
+ * Test that triggering a course_updated event works as expected.
+ */
+ public function test_course_updated_event() {
+ global $DB;
+
+ $this->resetAfterTest();
+
+ // Create a course.
+ $course = $this->getDataGenerator()->create_course();
+
+ // Create a category we are going to move this course to.
+ $category = $this->getDataGenerator()->create_category();
+
+ // Catch the update events.
+ $sink = $this->redirectEvents();
+
+ // Keep track of the old sortorder.
+ $sortorder = $course->sortorder;
+
+ // Call update_course which will trigger a course_updated event.
+ update_course($course);
+
+ // Return the updated course information from the DB.
+ $updatedcourse = $DB->get_record('course', array('id' => $course->id), '*', MUST_EXIST);
+
+ // Now move the course to the category, this will also trigger an event.
+ move_courses(array($course->id), $category->id);
+
+ // Return the moved course information from the DB.
+ $movedcourse = $DB->get_record('course', array('id' => $course->id), '*', MUST_EXIST);
+
+ // Now we want to set the sortorder back to what it was before fix_course_sortorder() was called. The reason for
+ // this is because update_course() and move_courses() call fix_course_sortorder() which alters the sort order in
+ // the DB, but it does not set the value of the sortorder for the course object passed to the event.
+ $updatedcourse->sortorder = $sortorder;
+ $movedcourse->sortorder = $category->sortorder + MAX_COURSES_IN_CATEGORY - 1;
+
+ // Capture the events.
+ $events = $sink->get_events();
+ $sink->close();
+
+ // Validate the events.
+ $event = $events[0];
+ $this->assertInstanceOf('\core\event\course_updated', $event);
+ $this->assertEquals('course', $event->objecttable);
+ $this->assertEquals($updatedcourse->id, $event->objectid);
+ $this->assertEquals(context_course::instance($updatedcourse->id)->id, $event->contextid);
+ $this->assertEquals($updatedcourse, $event->get_record_snapshot('course', $updatedcourse->id));
+ $this->assertEquals('course_updated', $event->get_legacy_eventname());
+ $this->assertEventLegacyData($updatedcourse, $event);
+ $expectedlog = array($updatedcourse->id, 'course', 'update', 'edit.php?id=' . $course->id, $course->id);
+ $this->assertEventLegacyLogData($expectedlog, $event);
+
+ $event = $events[1];
+ $this->assertInstanceOf('\core\event\course_updated', $event);
+ $this->assertEquals('course', $event->objecttable);
+ $this->assertEquals($movedcourse->id, $event->objectid);
+ $this->assertEquals(context_course::instance($movedcourse->id)->id, $event->contextid);
+ $this->assertEquals($movedcourse, $event->get_record_snapshot('course', $movedcourse->id));
+ $this->assertEquals('course_updated', $event->get_legacy_eventname());
+ $this->assertEventLegacyData($movedcourse, $event);
+ $expectedlog = array($movedcourse->id, 'course', 'move', 'edit.php?id=' . $movedcourse->id, $movedcourse->id);
+ $this->assertEventLegacyLogData($expectedlog, $event);
+ }
+
+ /**
+ * Test that triggering a course_deleted event works as expected.
+ */
+ public function test_course_deleted_event() {
+ $this->resetAfterTest();
+
+ // Create the course.
+ $course = $this->getDataGenerator()->create_course();
+
+ // Save the course context before we delete the course.
+ $coursecontext = context_course::instance($course->id);
+
+ // Catch the update event.
+ $sink = $this->redirectEvents();
+
+ // Call delete_course() which will trigger the course_deleted event and the course_content_deleted
+ // event. This function prints out data to the screen, which we do not want during a PHPUnit test,
+ // so use ob_start and ob_end_clean to prevent this.
+ ob_start();
+ delete_course($course);
+ ob_end_clean();
+
+ // Capture the event.
+ $events = $sink->get_events();
+ $sink->close();
+
+ // Validate the event.
+ $event = $events[1];
+ $this->assertInstanceOf('\core\event\course_deleted', $event);
+ $this->assertEquals('course', $event->objecttable);
+ $this->assertEquals($course->id, $event->objectid);
+ $this->assertEquals($coursecontext->id, $event->contextid);
+ $this->assertEquals($course, $event->get_record_snapshot('course', $course->id));
+ $this->assertEquals('course_deleted', $event->get_legacy_eventname());
+ // The legacy data also passed the context in the course object.
+ $course->context = $coursecontext;
+ $this->assertEventLegacyData($course, $event);
+ $expectedlog = array(SITEID, 'course', 'delete', 'view.php?id=' . $course->id, $course->fullname . '(ID ' . $course->id . ')');
+ $this->assertEventLegacyLogData($expectedlog, $event);
+ }
+
+ /**
+ * Test that triggering a course_content_deleted event works as expected.
+ */
+ public function test_course_content_deleted_event() {
+ global $DB;
+
+ $this->resetAfterTest();
+
+ // Create the course.
+ $course = $this->getDataGenerator()->create_course();
+
+ // Get the course from the DB. The data generator adds some extra properties, such as
+ // numsections, to the course object which will fail the assertions later on.
+ $course = $DB->get_record('course', array('id' => $course->id), '*', MUST_EXIST);
+
+ // Save the course context before we delete the course.
+ $coursecontext = context_course::instance($course->id);
+
+ // Catch the update event.
+ $sink = $this->redirectEvents();
+
+ // Call remove_course_contents() which will trigger the course_content_deleted event.
+ // This function prints out data to the screen, which we do not want during a PHPUnit
+ // test, so use ob_start and ob_end_clean to prevent this.
+ ob_start();
+ remove_course_contents($course->id);
+ ob_end_clean();
+
+ // Capture the event.
+ $events = $sink->get_events();
+ $sink->close();
+
+ // Validate the event.
+ $event = $events[0];
+ $this->assertInstanceOf('\core\event\course_content_deleted', $event);
+ $this->assertEquals('course', $event->objecttable);
+ $this->assertEquals($course->id, $event->objectid);
+ $this->assertEquals($coursecontext->id, $event->contextid);
+ $this->assertEquals($course, $event->get_record_snapshot('course', $course->id));
+ $this->assertEquals('course_content_removed', $event->get_legacy_eventname());
+ // The legacy data also passed the context and options in the course object.
+ $course->context = $coursecontext;
+ $course->options = array();
+ $this->assertEventLegacyData($course, $event);
+ }
+
+ /**
+ * Test that triggering a course_category_deleted event works as expected.
+ */
+ public function test_course_category_deleted_event() {
+ $this->resetAfterTest();
+
+ // Create a category.
+ $category = $this->getDataGenerator()->create_category();
+
+ // Save the context before it is deleted.
+ $categorycontext = context_coursecat::instance($category->id);
+
+ // Catch the update event.
+ $sink = $this->redirectEvents();
+
+ // Delete the category.
+ $category->delete_full();
+
+ // Capture the event.
+ $events = $sink->get_events();
+ $sink->close();
+
+ // Validate the event.
+ $event = $events[0];
+ $this->assertInstanceOf('\core\event\course_category_deleted', $event);
+ $this->assertEquals('course_categories', $event->objecttable);
+ $this->assertEquals($category->id, $event->objectid);
+ $this->assertEquals($categorycontext->id, $event->contextid);
+ $this->assertEquals('course_category_deleted', $event->get_legacy_eventname());
+ $this->assertEventLegacyData($category, $event);
+ $expectedlog = array(SITEID, 'category', 'delete', 'index.php', $category->name . '(ID ' . $category->id . ')');
+ $this->assertEventLegacyLogData($expectedlog, $event);
+
+ // Create two categories.
+ $category = $this->getDataGenerator()->create_category();
+ $category2 = $this->getDataGenerator()->create_category();
+
+ // Save the context before it is moved and then deleted.
+ $category2context = context_coursecat::instance($category2->id);
+
+ // Catch the update event.
+ $sink = $this->redirectEvents();
+
+ // Move the category.
+ $category2->delete_move($category->id);
+
+ // Capture the event.
+ $events = $sink->get_events();
+ $sink->close();
+
+ // Validate the event.
+ $event = $events[0];
+ $this->assertInstanceOf('\core\event\course_category_deleted', $event);
+ $this->assertEquals('course_categories', $event->objecttable);
+ $this->assertEquals($category2->id, $event->objectid);
+ $this->assertEquals($category2context->id, $event->contextid);
+ $this->assertEquals('course_category_deleted', $event->get_legacy_eventname());
+ $this->assertEventLegacyData($category2, $event);
+ $expectedlog = array(SITEID, 'category', 'delete', 'index.php', $category2->name . '(ID ' . $category2->id . ')');
+ $this->assertEventLegacyLogData($expectedlog, $event);
+ }
+
+ /**
+ * Test that triggering a course_restored event works as expected.
+ */
+ public function test_course_restored_event() {
+ global $CFG;
+
+ // Get the necessary files to perform backup and restore.
+ require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
+ require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
+
+ $this->resetAfterTest();
+
+ // Set to admin user.
+ $this->setAdminUser();
+
+ // The user id is going to be 2 since we are the admin user.
+ $userid = 2;
+
+ // Create a course.
+ $course = $this->getDataGenerator()->create_course();
+
+ // Create backup file and save it to the backup location.
+ $bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE,
+ backup::INTERACTIVE_NO, backup::MODE_GENERAL, $userid);
+ $bc->execute_plan();
+ $results = $bc->get_results();
+ $file = $results['backup_destination'];
+ $fp = get_file_packer();
+ $filepath = $CFG->dataroot . '/temp/backup/test-restore-course-event';
+ $file->extract_to_pathname($fp, $filepath);
+ $bc->destroy();
+ unset($bc);
+
+ // Now we want to catch the restore course event.
+ $sink = $this->redirectEvents();
+
+ // Now restore the course to trigger the event.
+ $rc = new restore_controller('test-restore-course-event', $course->id, backup::INTERACTIVE_NO,
+ backup::MODE_GENERAL, $userid, backup::TARGET_NEW_COURSE);
+ $rc->execute_precheck();
+ $rc->execute_plan();
+
+ // Capture the event.
+ $events = $sink->get_events();
+ $sink->close();
+
+ // Validate the event.
+ $event = $events[0];
+ $this->assertInstanceOf('\core\event\course_restored', $event);
+ $this->assertEquals('course', $event->objecttable);
+ $this->assertEquals($rc->get_courseid(), $event->objectid);
+ $this->assertEquals(context_course::instance($rc->get_courseid())->id, $event->contextid);
+ $this->assertEquals('course_restored', $event->get_legacy_eventname());
+ $legacydata = (object) array(
+ 'courseid' => $rc->get_courseid(),
+ 'userid' => $rc->get_userid(),
+ 'type' => $rc->get_type(),
+ 'target' => $rc->get_target(),
+ 'mode' => $rc->get_mode(),
+ 'operation' => $rc->get_operation(),
+ 'samesite' => $rc->is_samesite()
+ );
+ $this->assertEventLegacyData($legacydata, $event);
+
+ // Destroy the resource controller since we are done using it.
+ $rc->destroy();
+ unset($rc);
+
+ // Clear the time limit, otherwise PHPUnit complains.
+ set_time_limit(0);
+ }
+
+ /**
+ * Test that triggering a course_section_updated event works as expected.
+ */
+ public function test_course_section_updated_event() {
+ global $DB;
+
+ $this->resetAfterTest();
+
+ // Create the course with sections.
+ $course = $this->getDataGenerator()->create_course(array('numsections' => 10), array('createsections' => true));
+ $sections = $DB->get_records('course_sections', array('course' => $course->id));
+
+ $coursecontext = context_course::instance($course->id);
+
+ $section = array_pop($sections);
+ $section->name = 'Test section';
+ $section->summary = 'Test section summary';
+ $DB->update_record('course_sections', $section);
+
+ // Trigger an event for course section update.
+ $event = \core\event\course_section_updated::create(
+ array(
+ 'objectid' => $section->id,
+ 'courseid' => $course->id,
+ 'context' => context_course::instance($course->id)
+ )
+ );
+ $event->add_record_snapshot('course_sections', $section);
+ // Trigger and catch event.
+ $sink = $this->redirectEvents();
+ $event->trigger();
+ $events = $sink->get_events();
+ $sink->close();
+
+ // Validate the event.
+ $event = $events[0];
+ $this->assertInstanceOf('\core\event\course_section_updated', $event);
+ $this->assertEquals('course_sections', $event->objecttable);
+ $this->assertEquals($section->id, $event->objectid);
+ $this->assertEquals($course->id, $event->courseid);
+ $this->assertEquals($coursecontext->id, $event->contextid);
+ $expecteddesc = 'Course ' . $event->courseid . ' section ' . $event->other['sectionnum'] . ' updated by user ' . $event->userid;
+ $this->assertEquals($expecteddesc, $event->get_description());
+ $this->assertEquals($section, $event->get_record_snapshot('course_sections', $event->objectid));
+ $id = $section->id;
+ $sectionnum = $section->section;
+ $expectedlegacydata = array($course->id, "course", "editsection", 'editsection.php?id=' . $id, $sectionnum);
+ $this->assertEventLegacyLogData($expectedlegacydata, $event);
+ }
}
redirect($CFG->wwwroot .'/');
}
+ $ajaxenabled = ajaxenabled();
+
$completion = new completion_info($course);
- if ($completion->is_enabled() && ajaxenabled()) {
+ if ($completion->is_enabled() && $ajaxenabled) {
$PAGE->requires->string_for_js('completion-title-manual-y', 'completion');
$PAGE->requires->string_for_js('completion-title-manual-n', 'completion');
$PAGE->requires->string_for_js('completion-alt-manual-y', 'completion');
$PAGE->set_heading($course->fullname);
echo $OUTPUT->header();
- if ($completion->is_enabled() && ajaxenabled()) {
+ if ($completion->is_enabled() && $ajaxenabled) {
// This value tracks whether there has been a dynamic change to the page.
// It is used so that if a user does this - (a) set some tickmarks, (b)
// go to another page, (c) clicks Back button - the page will
resources.addClass(CSS.SECTION);
sectionnode.one('.'+CSS.CONTENT+' div.'+CSS.SUMMARY).insert(resources, 'after');
}
+ resources.setAttribute('data-draggroups', this.groups.join(' '));
// Define empty ul as droptarget, so that item could be moved to empty list
var tar = new Y.DD.Drop({
node: resources,
require_once("$CFG->libdir/clilib.php");
// Ensure errors are well explained.
-$CFG->debug = DEBUG_DEVELOPER;
+set_debugging(DEBUG_DEVELOPER, true);
if (!enrol_is_enabled('ldap')) {
cli_error(get_string('pluginnotenabled', 'enrol_ldap'), 2);
$this->assertTrue(enrol_user_sees_own_courses());
$this->assertEquals($reads, $DB->perf_get_reads());
}
+
+ public function test_enrol_get_shared_courses() {
+ $this->resetAfterTest();
+
+ $user1 = $this->getDataGenerator()->create_user();
+ $user2 = $this->getDataGenerator()->create_user();
+ $user3 = $this->getDataGenerator()->create_user();
+
+ $course1 = $this->getDataGenerator()->create_course();
+ $this->getDataGenerator()->enrol_user($user1->id, $course1->id);
+ $this->getDataGenerator()->enrol_user($user2->id, $course1->id);
+
+ $course2 = $this->getDataGenerator()->create_course();
+ $this->getDataGenerator()->enrol_user($user1->id, $course2->id);
+
+ // Test that user1 and user2 have courses in common.
+ $this->assertTrue(enrol_get_shared_courses($user1, $user2, false, true));
+ // Test that user1 and user3 have no courses in common.
+ $this->assertFalse(enrol_get_shared_courses($user1, $user3, false, true));
+
+ // Test retrieving the courses in common.
+ $sharedcourses = enrol_get_shared_courses($user1, $user2, true);
+
+ // Only should be one shared course.
+ $this->assertCount(1, $sharedcourses);
+ $sharedcourse = array_shift($sharedcourses);
+ // It should be course 1.
+ $this->assertEquals($sharedcourse->id, $course1->id);
+ }
}
.enrolpanel .container .header h2 {font-size:90%;text-align:center;margin:5px;}
.enrolpanel .container .header .close {width:25px;height:15px;position:absolute;top:5px;right:1em;cursor:pointer;background:url("sprite.png") no-repeat scroll 0 0 transparent;}
.enrolpanel .container .content {}
-.enrolpanel .container .content input {margin:5px;font-size:10px;}
\ No newline at end of file
+.enrolpanel .container .content input {margin:5px;font-size:10px;}
+.enrolpanel.roleassign.visible .container {width:auto;}
var roles = this.user.get(CONTAINER).one('.col_role .roles');
var x = roles.getX() + 10;
var y = roles.getY() + this.user.get(CONTAINER).get('offsetHeight') - 10;
- this.get('elementNode').setStyle('left', x).setStyle('top', y);
+ if ( Y.one(document.body).hasClass('dir-rtl') ) {
+ this.get('elementNode').setStyle('right', x - 20).setStyle('top', y);
+ } else {
+ this.get('elementNode').setStyle('left', x).setStyle('top', y);
+ }
this.get('elementNode').addClass('visible');
this.escCloseEvent = Y.on('key', this.hide, document.body, 'down:27', this);
this.displayed = true;
public static function get_files_parameters() {
return new external_function_parameters(
array(
- 'contextid' => new external_value(PARAM_INT, 'context id'),
- 'component' => new external_value(PARAM_TEXT, 'component'),
- 'filearea' => new external_value(PARAM_TEXT, 'file area'),
- 'itemid' => new external_value(PARAM_INT, 'associated id'),
- 'filepath' => new external_value(PARAM_PATH, 'file path'),
- 'filename' => new external_value(PARAM_FILE, 'file name'),
- 'modified' => new external_value(PARAM_INT, 'timestamp to return files changed after this time.', VALUE_DEFAULT, null)
+ 'contextid' => new external_value(PARAM_INT, 'context id Set to -1 to use contextlevel and instanceid.'),
+ 'component' => new external_value(PARAM_TEXT, 'component'),
+ 'filearea' => new external_value(PARAM_TEXT, 'file area'),
+ 'itemid' => new external_value(PARAM_INT, 'associated id'),
+ 'filepath' => new external_value(PARAM_PATH, 'file path'),
+ 'filename' => new external_value(PARAM_FILE, 'file name'),
+ 'modified' => new external_value(PARAM_INT, 'timestamp to return files changed after this time.', VALUE_DEFAULT, null),
+ 'contextlevel' => new external_value(PARAM_ALPHA, 'The context level for the file location.', VALUE_DEFAULT, null),
+ 'instanceid' => new external_value(PARAM_INT, 'The instance id for where the file is located.', VALUE_DEFAULT, null)
+
)
);
}
* @param string $filepath file path
* @param string $filename file name
* @param int $modified timestamp to return files changed after this time.
+ * @param string $contextlevel The context level for the file location.
+ * @param int $instanceid The instance id for where the file is located.
* @return array
* @since Moodle 2.2
*/
- public static function get_files($contextid, $component, $filearea, $itemid, $filepath, $filename, $modified = null) {
- global $CFG, $USER, $OUTPUT;
- $fileinfo = self::validate_parameters(self::get_files_parameters(), array(
- 'contextid'=>$contextid, 'component'=>$component, 'filearea'=>$filearea,
- 'itemid'=>$itemid, 'filepath'=>$filepath, 'filename'=>$filename, 'modified'=>$modified));
+ public static function get_files($contextid, $component, $filearea, $itemid, $filepath, $filename, $modified = null,
+ $contextlevel = null, $instanceid = null) {
+
+ $parameters = array(
+ 'contextid' => $contextid,
+ 'component' => $component,
+ 'filearea' => $filearea,
+ 'itemid' => $itemid,
+ 'filepath' => $filepath,
+ 'filename' => $filename,
+ 'modified' => $modified,
+ 'contextlevel' => $contextlevel,
+ 'instanceid' => $instanceid);
+ $fileinfo = self::validate_parameters(self::get_files_parameters(), $parameters);
$browser = get_file_browser();
- if (empty($fileinfo['contextid'])) {
- $context = context_system::instance();
+ // We need to preserve backwards compatibility. Zero will use the system context and minus one will
+ // use the addtional parameters to determine the context.
+ // TODO MDL-40489 get_context_from_params should handle this logic.
+ if ($fileinfo['contextid'] == 0) {
+ $context = context_system::instance();
} else {
- $context = context::instance_by_id($fileinfo['contextid']);
+ if ($fileinfo['contextid'] == -1) {
+ $fileinfo['contextid'] = null;
+ }
+ $context = self::get_context_from_params($fileinfo);
}
+ self::validate_context($context);
+
if (empty($fileinfo['component'])) {
$fileinfo['component'] = null;
}
$return['parents'] = array();
$return['files'] = array();
$list = array();
+
if ($file = $browser->get_file_info(
$context, $fileinfo['component'], $fileinfo['filearea'], $fileinfo['itemid'],
$fileinfo['filepath'], $fileinfo['filename'])) {
$filepath = '/';
}
+ // Only allow uploads to draft or private areas (private is deprecated but still supported)
+ if (!($fileinfo['component'] == 'user' and in_array($fileinfo['filearea'], array('private', 'draft')))) {
+ throw new coding_exception('File can be uploaded to user private or draft areas only');
+ } else {
+ $component = 'user';
+ $filearea = $fileinfo['filearea'];
+ }
+
+ $itemid = 0;
if (isset($fileinfo['itemid'])) {
+ $itemid = $fileinfo['itemid'];
+ }
+ if ($filearea == 'draft' && $itemid <= 0) {
+ // Generate a draft area for the files.
+ $itemid = file_get_unused_draft_itemid();
+ } else if ($filearea == 'private') {
// TODO MDL-31116 in user private area, itemid is always 0.
$itemid = 0;
- } else {
- throw new coding_exception('itemid cannot be empty');
}
// We need to preserve backword compatibility. Context id is no more a required.
// Get and validate context.
$context = self::get_context_from_params($fileinfo);
self::validate_context($context);
-
- if (!($fileinfo['component'] == 'user' and $fileinfo['filearea'] == 'private')) {
- throw new coding_exception('File can be uploaded to user private area only');
- } else {
- // TODO MDL-31116 hard-coded to use user_private area.
- $component = 'user';
- $filearea = 'private';
+ if (($fileinfo['component'] == 'user' and $fileinfo['filearea'] == 'private')) {
+ debugging('Uploading directly to user private files area is deprecated. Upload to a draft area and then move the files with core_user::add_user_private_files');
}
$browser = get_file_browser();
$module = array(
'name'=>'form_filemanager',
'fullpath'=>'/lib/form/filemanager.js',
- 'requires' => array('core_filepicker', 'base', 'io-base', 'node', 'json', 'core_dndupload', 'panel', 'resize-plugin', 'dd-plugin'),
+ 'requires' => array('moodle-core-notification-dialogue', 'core_filepicker', 'base', 'io-base', 'node', 'json', 'core_dndupload', 'panel', 'resize-plugin', 'dd-plugin'),
'strings' => array(
array('error', 'moodle'), array('info', 'moodle'), array('confirmdeletefile', 'repository'),
array('draftareanofiles', 'repository'), array('entername', 'repository'), array('enternewname', 'repository'),
array('invalidjson', 'repository'), array('popupblockeddownload', 'repository'),
array('unknownoriginal', 'repository'), array('confirmdeletefolder', 'repository'),
array('confirmdeletefilewithhref', 'repository'), array('confirmrenamefolder', 'repository'),
- array('confirmrenamefile', 'repository'), array('newfolder', 'repository')
+ array('confirmrenamefile', 'repository'), array('newfolder', 'repository'), array('edit', 'moodle')
)
);
if (empty($filemanagertemplateloaded)) {
<li class="{!}fp-repo"><a href="#"><img class="{!}fp-repo-icon" alt="'. get_string('repositoryicon', 'repository') .'" width="16" height="16" /> <span class="{!}fp-repo-name"></span></a></li>
</ul>
</div>
- <div class="fp-repo-items">
+ <div class="fp-repo-items" tabindex="0">
<div class="fp-navbar">
<div>
<div class="{!}fp-toolbar">
$context = context_user::instance($USER->id);
$contextid = $context->id;
$component = "user";
- $filearea = "private";
+ $filearea = "draft";
$itemid = 0;
$filepath = "/";
$filename = "Simple.txt";
$this->assertEmpty($file);
// Call the api to create a file.
- core_files_external::upload($contextid, $component, $filearea, $itemid, $filepath,
+ $fileinfo = core_files_external::upload($contextid, $component, $filearea, $itemid, $filepath,
$filename, $filecontent, $contextlevel, $instanceid);
+ // Get the created draft item id.
+ $itemid = $fileinfo['itemid'];
// Make sure the file was created.
$file = $browser->get_file_info($context, $component, $filearea, $itemid, $filepath, $filename);
$this->assertNotEmpty($file);
// Make sure no file exists.
- $itemid = 2;
$filename = "Simple2.txt";
$file = $browser->get_file_info($context, $component, $filearea, $itemid, $filepath, $filename);
$this->assertEmpty($file);
// Call the api to create a file.
$fileinfo = core_files_external::upload($contextid, $component, $filearea, $itemid,
$filepath, $filename, $filecontent, $contextlevel, $instanceid);
-
- // Make sure itemid is always set to 0.
- $this->assertEquals(0, $fileinfo['itemid']);
+ $file = $browser->get_file_info($context, $component, $filearea, $itemid, $filepath, $filename);
+ $this->assertNotEmpty($file);
// Let us try creating a file using contextlevel and instance id.
- $itemid = 0;
$filename = "Simple5.txt";
$contextid = 0;
$contextlevel = "user";
$this->assertEmpty($file);
$fileinfo = core_files_external::upload($contextid, $component, $filearea, $itemid, $filepath,
$filename, $filecontent, $contextlevel, $instanceid);
- $this->assertEmpty($file);
+ $file = $browser->get_file_info($context, $component, $filearea, $itemid, $filepath, $filename);
+ $this->assertNotEmpty($file);
// Make sure the same file cannot be created again.
$this->setExpectedException("moodle_exception");
}
/*
- * Make sure only private area is allowed in core_files_external::upload().
+ * Make sure only private or draft areas are allowed in core_files_external::upload().
*/
public function test_upload_param_area() {
global $USER;
$contextid = $context->id;
$component = "user";
$filearea = "draft";
- $itemid = 0;
+ $itemid = file_get_unused_draft_itemid();
$filepath = "/";
$filename = "Simple4.txt";
$filecontent = base64_encode("Let us create a nice simple file");
$contextlevel = null;
$instanceid = null;
- // Make sure exception is thrown.
- $this->setExpectedException("coding_exception");
- core_files_external::upload($contextid, $component, $filearea, $itemid, $filepath,
- $filename, $filecontent, $contextlevel, $instanceid);
+ // Make sure the file is created.
+ @core_files_external::upload($contextid, $component, $filearea, $itemid, $filepath, $filename, $filecontent);
+ $browser = get_file_browser();
+ $file = $browser->get_file_info($context, $component, $filearea, $itemid, $filepath, $filename);
+ $this->assertNotEmpty($file);
}
/*
$filename = "Simple4.txt";
$filecontent = base64_encode("Let us create a nice simple file");
- // Make sure the file is created.
@core_files_external::upload($contextid, $component, $filearea, $itemid, $filepath, $filename, $filecontent);
+
+ // Assert debugging called (deprecation warning).
+ $this->assertDebuggingCalled();
+
+ // Make sure the file is created.
$browser = get_file_browser();
$file = $browser->get_file_info($context, $component, $filearea, $itemid, $filepath, $filename);
$this->assertNotEmpty($file);
}
-}
\ No newline at end of file
+
+ public function test_get_files() {
+ global $USER, $DB;
+
+ $this->resetAfterTest();
+
+ $this->setAdminUser();
+ $USER->email = 'test@moodle.com';
+
+ $course = $this->getDataGenerator()->create_course();
+ $record = new stdClass();
+ $record->course = $course->id;
+ $record->name = "Mod data upload test";
+
+ $record->intro = "Some intro of some sort";
+
+ $module = $this->getDataGenerator()->create_module('data', $record);
+
+ $field = data_get_field_new('file', $module);
+
+ $fielddetail = new stdClass();
+ $fielddetail->d = $module->id;
+ $fielddetail->mode = 'add';
+ $fielddetail->type = 'file';
+ $fielddetail->sesskey = sesskey();
+ $fielddetail->name = 'Upload file';
+ $fielddetail->description = 'Some description';
+ $fielddetail->param3 = '0';
+
+ $field->define_field($fielddetail);
+ $field->insert_field();
+ $recordid = data_add_record($module);
+
+ $timemodified = $DB->get_field('data_records', 'timemodified', array('id' => $recordid));
+
+ $datacontent = array();
+ $datacontent['fieldid'] = $field->field->id;
+ $datacontent['recordid'] = $recordid;
+ $datacontent['content'] = 'Simple4.txt';
+
+ $contentid = $DB->insert_record('data_content', $datacontent);
+
+ $context = context_module::instance($module->id);
+ $usercontext = context_user::instance($USER->id);
+ $component = 'mod_data';
+ $filearea = 'content';
+ $itemid = $contentid;
+ $filename = $datacontent['content'];
+ $filecontent = base64_encode("Let us create a nice simple file.");
+
+ $filerecord = array();
+ $filerecord['contextid'] = $context->id;
+ $filerecord['component'] = $component;
+ $filerecord['filearea'] = $filearea;
+ $filerecord['itemid'] = $itemid;
+ $filerecord['filepath'] = '/';
+ $filerecord['filename'] = $filename;
+
+ $fs = get_file_storage();
+ $file = $fs->create_file_from_string($filerecord, $filecontent);
+
+ $filename = '';
+ $testfilelisting = core_files_external::get_files($context->id, $component, $filearea, $itemid, '/', $filename);
+
+ $testdata = array();
+ $testdata['parents'] = array();
+ $testdata['parents']['0'] = array('contextid' => 1,
+ 'component' => null,
+ 'filearea' => null,
+ 'itemid' => null,
+ 'filepath' => null,
+ 'filename' => 'System');
+ $testdata['parents']['1'] = array('contextid' => 3,
+ 'component' => null,
+ 'filearea' => null,
+ 'itemid' => null,
+ 'filepath' => null,
+ 'filename' => 'Miscellaneous');
+ $testdata['parents']['2'] = array('contextid' => 15,
+ 'component' => null,
+ 'filearea' => null,
+ 'itemid' => null,
+ 'filepath' => null,
+ 'filename' => 'Test course 1');
+ $testdata['parents']['3'] = array('contextid' => 20,
+ 'component' => null,
+ 'filearea' => null,
+ 'itemid' => null,
+ 'filepath' => null,
+ 'filename' => 'Mod data upload test (Database)');
+ $testdata['parents']['4'] = array('contextid' => 20,
+ 'component' => 'mod_data',
+ 'filearea' => 'content',
+ 'itemid' => null,
+ 'filepath' => null,
+ 'filename' => 'Fields');
+ $testdata['files'] = array();
+ $testdata['files']['0'] = array('contextid' => 20,
+ 'component' => 'mod_data',
+ 'filearea' => 'content',
+ 'itemid' => 1,
+ 'filepath' => '/',
+ 'filename' => 'Simple4.txt',
+ 'url' => 'http://www.example.com/moodle/pluginfile.php/20/mod_data/content/1/Simple4.txt',
+ 'isdir' => null,
+ 'timemodified' => $timemodified);
+
+ $this->assertEquals($testfilelisting, $testdata);
+
+ // Try again but without the context.
+ $nocontext = -1;
+ $modified = 0;
+ $contextlevel = 'module';
+ $instanceid = $module->id;
+ $testfilelisting = core_files_external::get_files($nocontext, $component, $filearea, $itemid, '/', $filename, $modified, $contextlevel, $instanceid);
+ $this->assertEquals($testfilelisting, $testdata);
+ }
+}
if ($type == 'grade' and empty($object->id)) {
$object->insert();
}
+ if (!$object->can_control_visibility()) {
+ print_error('componentcontrolsvisibility', 'grades', $returnurl);
+ }
$object->set_hidden(1, true);
}
break;
if ($type == 'grade' and empty($object->id)) {
$object->insert();
}
+ if (!$object->can_control_visibility()) {
+ print_error('componentcontrolsvisibility', 'grades', $returnurl);
+ }
$object->set_hidden(0, true);
}
break;
public $columns = array();
/**
- * @var object $gtree @see grade/lib.php
+ * @var grade_tree $gtree @see grade/lib.php
*/
public $gtree;
.gradingform_rubric .plainvalue.empty {font-style: italic; color: #AAA;}
.gradingform_rubric.editor .criterion .levels .level .delete {position:absolute;right:0;}
+.dir-rtl .gradingform_rubric.editor .criterion .levels .level .delete {position: relative;}
.gradingform_rubric .criterion .levels .level .score {font-style:italic;color:#575;font-weight: bold;margin-top:5px;white-space:nowrap;}
.gradingform_rubric .criterion .levels .level .score .scorevalue {padding-right:5px;}
public function get_hiding_icon($element, $gpr) {
global $CFG, $OUTPUT;
+ if (!$element['object']->can_control_visibility()) {
+ return '';
+ }
+
if (!has_capability('moodle/grade:manage', $this->context) and
!has_capability('moodle/grade:hide', $this->context)) {
return '';
* @return bool
*/
public function is_fixed_students() {
- global $USER, $CFG;
+ global $CFG;
return $CFG->grade_report_fixedstudents &&
- (check_browser_version('MSIE', '7.0') ||
- check_browser_version('Firefox', '2.0') ||
- check_browser_version('Gecko', '2006010100') ||
- check_browser_version('Camino', '1.0') ||
- check_browser_version('Opera', '6.0') ||
- check_browser_version('Chrome', '6') ||
- check_browser_version('Safari', '300'));
+ (core_useragent::check_ie_version('7.0') ||
+ core_useragent::check_firefox_version('2.0') ||
+ core_useragent::check_gecko_version('2006010100') ||
+ core_useragent::check_camino_version('1.0') ||
+ core_useragent::check_opera_version('6.0') ||
+ core_useragent::check_chrome_version('6') ||
+ core_useragent::check_safari_version('300'));
}
/**
require('tabs.php');
$disabled = 'disabled="disabled"';
-if (ajaxenabled()) {
+$ajaxenabled = ajaxenabled();
+if ($ajaxenabled) {
// Some buttons are enabled if single group selected
$showaddmembersform_disabled = $singlegroup ? '' : $disabled;
$showeditgroupsettingsform_disabled = $singlegroup ? '' : $disabled;
echo "<td>\n";
echo '<p><label for="groups"><span id="groupslabel">'.get_string('groups').':</span><span id="thegrouping"> </span></label></p>'."\n";
-if (ajaxenabled()) { // TODO: move this to JS init!
+if ($ajaxenabled) { // TODO: move this to JS init!
$onchange = 'M.core_group.membersCombo.refreshMembers();';
} else {
$onchange = '';
echo '</div>'."\n";
echo '</form>'."\n";
-if (ajaxenabled()) {
+if ($ajaxenabled) {
$PAGE->requires->js_init_call('M.core_group.init_index', array($CFG->wwwroot, $courseid));
$PAGE->requires->js_init_call('M.core_group.groupslist', array($preventgroupremoval));
}
$CFG->running_installer = true;
$CFG->early_install_lang = true;
$CFG->ostype = (stristr(PHP_OS, 'win') && !stristr(PHP_OS, 'darwin')) ? 'WINDOWS' : 'UNIX';
+$CFG->debug = (E_ALL | E_STRICT);
+$CFG->debugdisplay = true;
+$CFG->debugdeveloper = true;
// Require all needed libs
require_once($CFG->libdir.'/setuplib.php');
// Continue with lib loading
require_once($CFG->libdir.'/classes/text.php');
+require_once($CFG->libdir.'/classes/string_manager.php');
+require_once($CFG->libdir.'/classes/string_manager_install.php');
+require_once($CFG->libdir.'/classes/string_manager_standard.php');
require_once($CFG->libdir.'/weblib.php');
require_once($CFG->libdir.'/outputlib.php');
require_once($CFG->libdir.'/dmllib.php');
--- /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/>.
+
+/**
+ * Automatically generated strings for Moodle installer
+ *
+ * Do not edit this file manually! It contains just a subset of strings
+ * needed during the very first steps of installation. This file was
+ * generated automatically by export-installer.php (which is part of AMOS
+ * {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
+ * list of strings defined in /install/stringnames.txt.
+ *
+ * @package installer
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$string['clianswerno'] = 'एन';
+$string['cliansweryes'] = 'वाई';
$string['errorminpasswordupper'] = 'Passwords must have at least {$a} upper case letter(s).';
$string['errorpasswordupdate'] = 'Error updating password, password not changed';
$string['event_user_loggedin'] = 'User has logged in';
+$string['eventuserloggedinas'] = 'User logged in as another user';
$string['forcechangepassword'] = 'Force change password';
$string['forcechangepasswordfirst_help'] = 'Force users to change password on their first login to Moodle.';
$string['forcechangepassword_help'] = 'Force users to change password on their next login to Moodle.';
$string['entrytitle'] = 'Entry title';
$string['entryupdated'] = 'Blog entry updated';
$string['evententryadded'] = 'Blog entry added';
+$string['evententrydeleted'] = 'Blog entry deleted';
$string['externalblogcrontime'] = 'External blog cron schedule';
$string['externalblogdeleteconfirm'] = 'Unregister this external blog?';
$string['externalblogdeleted'] = 'External blog unregistered';
$string['cachedef_eventinvalidation'] = 'Event invalidation';
$string['cachedef_groupdata'] = 'Course group information';
$string['cachedef_htmlpurifier'] = 'HTML Purifier - cleaned content';
+$string['cachedef_langmenu'] = 'List of available languages';
$string['cachedef_locking'] = 'Locking';
$string['cachedef_observers'] = 'Event observers';
$string['cachedef_plugininfo_base'] = 'Plugin info - base';
$string['description'] = 'Description';
$string['duplicateidnumber'] = 'Cohort with the same ID number already exists';
$string['editcohort'] = 'Edit cohort';
+$string['event_cohort_created'] = 'Cohort created';
+$string['event_cohort_deleted'] = 'Cohort deleted';
+$string['event_cohort_member_added'] = 'User added to a cohort';
+$string['event_cohort_member_removed'] = 'User removed from a cohort';
+$string['event_cohort_updated'] = 'Cohort updated';
$string['external'] = 'External cohort';
$string['idnumber'] = 'Cohort ID';
$string['memberscount'] = 'Cohort size';
$string['err_nousers'] = 'There are no students on this course or group for whom completion information is displayed. (By default, completion information is displayed only for students, so if there are no students, you will see this error. Administrators can alter this option via the admin screens.)';
$string['err_settingslocked'] = 'One or more students have already completed a criteria so the settings have been locked. Unlocking the completion criteria settings will delete any existing user data and may cause confusion.';
$string['err_system'] = 'An internal error occurred in the completion system. (System administrators can enable debugging information to see more detail.)';
+$string['eventcoursecompleted'] = 'Course completed';
+$string['eventcoursemodulecompletionupdated'] = 'Course module completion updated';
$string['excelcsvdownload'] = 'Download in Excel-compatible format (.csv)';
$string['fraction'] = 'Fraction';
$string['graderequired'] = 'Required course grade';
$string['err_required'] = 'You must supply a value here.';
$string['general'] = 'General';
$string['hideadvanced'] = 'Hide advanced';
-$string['hideeditortoolbar'] = 'Hide editing tools';
$string['hour'] = 'Hour';
$string['minute'] = 'Minute';
$string['miscellaneoussettings'] = 'Miscellaneous settings';
$string['showadvanced'] = 'Show advanced';
$string['showless'] = 'Show less...';
$string['showmore'] = 'Show more...';
-$string['showeditortoolbar'] = 'Show editing tools';
$string['somefieldsrequired'] = 'There are required fields in this form marked {$a}.';
$string['time'] = 'Time';
$string['timeunit'] = 'Time unit';
<p>It contains easy instructions to complete your registration.</p>
<p>If you continue to have difficulty, contact the site administrator.</p>';
$string['emaildigest'] = 'Email digest type';
+$string['emaildigest_help'] = 'This is the daily digest setting that forums will use by default.
+
+* No digest - you will receive one e-mail per forum post;
+* Digest - complete posts - you will receive one digest e-mail per day containing the complete contents of each forum post;
+* Digest - subjects only - you will receive one digest e-mail per day containing just the subject of each forum post.
+
+You can also choose a different setting for each forum if you wish.';
$string['emaildigestcomplete'] = 'Complete (daily email with full posts)';
$string['emaildigestoff'] = 'No digest (single email per forum post)';
$string['emaildigestsubjects'] = 'Subjects (daily email with subjects only)';
An email containing your new password has been sent to your address at<br /><b>{$a->email}</b>.<br />
The new password was automatically generated - you might like to
<a href="{$a->link}">change your password</a> to something easier to remember.';
+$string['emptydragdropregion'] = 'empty region';
$string['enable'] = 'Enable';
$string['encryptedcode'] = 'Encrypted code';
$string['english'] = 'English';
$string['errorfiletoobig'] = 'The file was bigger than the limit of {$a} bytes';
$string['errornouploadrepo'] = 'There is no upload repository enabled for this site';
$string['errorwhenconfirming'] = 'You are not confirmed yet because an error occurred. If you clicked on a link in an email to get here, make sure that the line in your email wasn\'t broken or wrapped. You may have to use cut and paste to reconstruct the link properly.';
+$string['eventcoursecategorydeleted'] = 'Category deleted';
+$string['eventcoursecontentdeleted'] = 'Course content deleted';
+$string['eventcoursecreated'] = 'Course created';
+$string['eventcoursedeleted'] = 'Course deleted';
+$string['eventcourserestored'] = 'Course restored';
+$string['eventcourseupdated'] = 'Course updated';
+$string['eventcoursesectionupdated'] = ' Course section updated';
$string['everybody'] = 'Everybody';
$string['executeat'] = 'Execute at';
$string['existing'] = 'Existing';
$string['moreprofileinfoneeded'] = 'Please tell us more about yourself';
$string['mostrecently'] = 'most recently';
$string['move'] = 'Move';
+$string['movecontent'] = 'Move {$a}';
$string['movecategorycontentto'] = 'Move into';
$string['movecategoryto'] = 'Move category to:';
$string['movecontentstoanothercategory'] = 'Move contents to another category';
$string['timezone'] = 'Timezone';
$string['to'] = 'To';
$string['tocreatenewaccount'] = 'Skip to create new account';
+$string['tocontent'] = 'To item "{$a}"';
$string['today'] = 'Today';
$string['todaylogs'] = 'Today\'s logs';
$string['toeveryone'] = 'to everyone';
$string['errorbadroleshortname'] = 'Incorrect role short name';
$string['errorexistsrolename'] = 'Role name already exists';
$string['errorexistsroleshortname'] = 'Role name already exists';
+$string['eventroleallowassignupdated'] = 'Allow role assignment';
+$string['eventroleallowoverrideupdated'] = 'Allow role override';
+$string['eventroleallowswitchupdated'] = 'Allow role switch';
$string['eventroleassigned'] = 'Role assigned';
+$string['eventrolecapabilitiesupdated'] = 'Role capabilities updated';
+$string['eventroledeleted'] = 'Role deleted';
$string['eventroleunassigned'] = 'Role unassigned';
$string['existingadmins'] = 'Current site administrators';
$string['existingusers'] = '{$a} existing users';
$string['unknownoptionkey'] = 'Unknown option key ({$a})';
$string['unnamedstringparam'] = 'A string parameter is unnamed.';
$string['updateusersettings'] = 'Update';
+$string['uploadfiles'] = 'Can upload files';
+$string['uploadfiles_help'] = 'If enabled, any user can upload files with their security keys to their own private files area or a draft file area. Any user file quotas apply.';
$string['userasclients'] = 'Users as clients with token';
$string['userasclientsdescription'] = 'The following steps help you to set up the Moodle web service for users as clients. These steps also help to set up the recommended token (security keys) authentication method. In this use case, the user will generate his token from the security keys page via My profile settings.';
$string['usermissingcaps'] = 'Missing capabilities: {$a}';
$DB->delete_records('role_names', array('roleid'=>$roleid));
$DB->delete_records('role_context_levels', array('roleid'=>$roleid));
- // finally delete the role itself
- // get this before the name is gone for logging
- $rolename = $DB->get_field('role', 'name', array('id'=>$roleid));
+ // Get role record before it's deleted.
+ $role = $DB->get_record('role', array('id'=>$roleid));
+ // Finally delete the role itself.
$DB->delete_records('role', array('id'=>$roleid));
- add_to_log(SITEID, 'role', 'delete', 'admin/roles/action=delete&roleid='.$roleid, $rolename, '');
+ // Trigger event.
+ $event = \core\event\role_deleted::create(
+ array(
+ 'context' => context_system::instance(),
+ 'objectid' => $roleid,
+ 'other' =>
+ array(
+ 'name' => $role->name,
+ 'shortname' => $role->shortname,
+ 'description' => $role->description,
+ 'archetype' => $role->archetype
+ )
+ )
+ );
+ $event->add_record_snapshot('role', $role);
+ $event->trigger();
return true;
}
$fields = 'u.*';
}
} else {
- if (debugging('', DEBUG_DEVELOPER) && strpos($fields, 'u.*') === false && strpos($fields, 'u.id') === false) {
+ if ($CFG->debugdeveloper && strpos($fields, 'u.*') === false && strpos($fields, 'u.id') === false) {
debugging('u.id must be included in the list of fields passed to get_users_by_capability().', DEBUG_DEVELOPER);
}
}
* @return bool True if successfully added, false if $something can not be added.
*/
public function add($parentname, $something, $beforesibling = null) {
+ global $CFG;
+
$parent = $this->locate($parentname);
if (is_null($parent)) {
debugging('parent does not exist!');
debugging('error - parts of tree can be inserted only into parentable parts');
return false;
}
- if (debugging('', DEBUG_DEVELOPER) && !is_null($this->locate($something->name))) {
+ if ($CFG->debugdeveloper && !is_null($this->locate($something->name))) {
// The name of the node is already used, simply warn the developer that this should not happen.
// It is intentional to check for the debug level before performing the check.
debugging('Duplicate admin page name: ' . $something->name, DEBUG_DEVELOPER);
+++ /dev/null
-<?php
-// This file is&