} else {
$str = 'otherplugin';
}
+ $componenturl = new moodle_url('https://moodle.org/plugins/view.php?plugin='.$component);
+ $componenturl = html_writer::tag('a', $component, array('href' => $componenturl->out()));
$requires[] = html_writer::tag('li',
get_string($str, 'core_plugin',
- array('component' => $component, 'version' => $requiredversion)),
+ array('component' => $componenturl, 'version' => $requiredversion)),
array('class' => $class));
}
$success = $repositorytype->update_options($settings);
} else {
$type = new repository_type($plugin, (array)$fromform, $visible);
- $type->create();
$success = true;
+ if (!$repoid = $type->create()) {
+ $success = false;
+ }
$data = data_submitted();
}
if ($success) {
$this->displaypermissions = $this->allpermissions;
$this->strperms[$this->allpermissions[CAP_INHERIT]] = get_string('notset', 'core_role');
- $this->allcontextlevels = array(
- CONTEXT_SYSTEM => get_string('coresystem'),
- CONTEXT_USER => get_string('user'),
- CONTEXT_COURSECAT => get_string('category'),
- CONTEXT_COURSE => get_string('course'),
- CONTEXT_MODULE => get_string('activitymodule'),
- CONTEXT_BLOCK => get_string('block')
- );
+ $this->allcontextlevels = array();
+ $levels = context_helper::get_all_levels();
+ foreach ($levels as $level => $classname) {
+ $this->allcontextlevels[$level] = context_helper::get_level_name($level);
+ }
}
protected function load_current_permissions() {
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+/**
+ * Backend generic code.
+ *
+ * @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();
/**
- * Backend code for the 'make large course' tool.
+ * Backend generic code for all tool_generator commands.
*
+ * @abstract
* @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 {
+abstract class tool_generator_backend {
/**
* @var int Lowest (smallest) 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)
+ * @var bool True if we want a fixed dataset or false to generate random data
*/
- private $size;
+ protected $fixeddataset;
/**
* @var bool True if displaying progress
*/
- private $progress;
-
- /**
- * @var testing_data_generator Data generator
- */
- private $generator;
-
- /**
- * @var stdClass Course object
- */
- private $course;
+ protected $progress;
/**
* @var int Epoch time at which last dot was displayed
*/
- private $lastdot;
+ protected $lastdot;
/**
* @var int Epoch time at which last percentage was displayed
*/
- private $lastpercentage;
+ protected $lastpercentage;
/**
* @var int Epoch time at which current step (current set of dots) started
*/
- private $starttime;
+ protected $starttime;
/**
- * @var array Array from test user number (1...N) to userid in database
+ * @var int Size code (index in the above arrays)
*/
- private $userids;
+ protected $size;
/**
- * Constructs object ready to create course.
+ * Generic generator class
*
- * @param string $shortname Course shortname
* @param int $size Size as numeric index
+ * @param bool $fixeddataset To use fixed or random data
* @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) {
+ public function __construct($size, $fixeddataset = false, $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->fixeddataset = $fixeddataset;
$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.
*
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) {
+ protected function log($langstring, $a = null, $leaveopen = false) {
if (!$this->progress) {
return;
}
* @param int $number Number of completed items
* @param int $total Total number of items to complete
*/
- private function dot($number, $total) {
+ protected function dot($number, $total) {
if (!$this->progress) {
return;
}
/**
* Ends a log string that was started using log function with $leaveopen.
*/
- private function end_log() {
+ protected function end_log() {
if (!$this->progress) {
return;
}
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/>.
+
+/**
+ * tool_generator course backend code.
+ *
+ * @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();
+
+/**
+ * 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_course_backend extends tool_generator_backend {
+ /**
+ * @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 testing_data_generator Data generator
+ */
+ protected $generator;
+
+ /**
+ * @var stdClass Course object
+ */
+ private $course;
+
+ /**
+ * @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 $fixeddataset To use fixed or random data
+ * @param bool $progress True if progress information should be displayed
+ * @return int Course id
+ */
+ public function __construct($shortname, $size, $fixeddataset = false, $progress = true) {
+
+ // Set parameters.
+ $this->shortname = $shortname;
+
+ parent::__construct($size, $fixeddataset, $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('coursesize_' . $size, 'tool_generator');
+ }
+ return $options;
+ }
+
+ /**
+ * 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('coursecompleted', 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);
+ }
+
+ // Sets the pointer at the beginning to be aware of the users we use.
+ reset($this->userids);
+
+ $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_target_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_target_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_target_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_target_user(), 'parent' => $parentid);
+ $forumgenerator->create_post($record);
+ $this->dot($sofar, $totalposts);
+ }
+ }
+
+ $this->end_log();
+ }
+
+ /**
+ * Gets a section number.
+ *
+ * Depends on $this->fixeddataset.
+ *
+ * @return int A section number from 1 to the number of sections
+ */
+ private function get_target_section() {
+
+ if (!$this->fixeddataset) {
+ $key = rand(1, self::$paramsections[$this->size]);
+ } else {
+ // Using section 1.
+ $key = 1;
+ }
+
+ return $key;
+ }
+
+ /**
+ * Gets a user id.
+ *
+ * Depends on $this->fixeddataset.
+ *
+ * @return int A user id for a random created user
+ */
+ private function get_target_user() {
+
+ if (!$this->fixeddataset) {
+ $userid = $this->userids[rand(1, self::$paramusers[$this->size])];
+ } else if ($userid = current($this->userids)) {
+ // Moving pointer to the next user.
+ next($this->userids);
+ } else {
+ // Returning to the beginning if we reached the end.
+ $userid = reset($this->userids);
+ }
+
+ return $userid;
+ }
+
+}
$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);
+ tool_generator_course_backend::get_size_choices());
+ $mform->setDefault('size', tool_generator_course_backend::DEFAULT_SIZE);
$mform->addElement('text', 'shortname', get_string('shortnamecourse'));
$mform->addRule('shortname', get_string('missingshortname'), 'required', null, 'client');
// Check course doesn't already exist.
if (!empty($data['shortname'])) {
// Check shortname.
- $error = tool_generator_backend::check_shortname_available($data['shortname']);
+ $error = tool_generator_course_backend::check_shortname_available($data['shortname']);
if ($error) {
$errors['shortname'] = $error;
}
--- /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/>.
+
+/**
+ * tool_generator site backend.
+ *
+ * @package tool_generator
+ * @copyright 2013 David Monllaó
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Backend code for the site generator.
+ *
+ * @package tool_generator
+ * @copyright 2013 David Monllaó
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class tool_generator_site_backend extends tool_generator_backend {
+
+ /**
+ * @var string The course's shortname prefix.
+ */
+ const SHORTNAMEPREFIX = 'testcourse_';
+
+ /**
+ * @var bool If the debugging level checking was skipped.
+ */
+ protected $bypasscheck;
+
+ /**
+ * @var array Multidimensional array where the first level is the course size and the second the site size.
+ */
+ protected static $sitecourses = array(
+ array(2, 8, 64, 256, 1024, 4096),
+ array(1, 4, 8, 16, 32, 64),
+ array(0, 0, 1, 4, 8, 16),
+ array(0, 0, 0, 1, 0, 0),
+ array(0, 0, 0, 0, 1, 0),
+ array(0, 0, 0, 0, 0, 1)
+ );
+
+ /**
+ * Constructs object ready to make the site.
+ *
+ * @param int $size Size as numeric index
+ * @param bool $bypasscheck If debugging level checking was skipped.
+ * @param bool $fixeddataset To use fixed or random data
+ * @param bool $progress True if progress information should be displayed
+ * @return int Course id
+ */
+ public function __construct($size, $bypasscheck, $fixeddataset = false, $progress = true) {
+
+ // Set parameters.
+ $this->bypasscheck = $bypasscheck;
+
+ parent::__construct($size, $fixeddataset, $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('sitesize_' . $size, 'tool_generator');
+ }
+ return $options;
+ }
+
+ /**
+ * Runs the entire 'make' process.
+ *
+ * @return int Course id
+ */
+ public function make() {
+ global $DB, $CFG;
+
+ raise_memory_limit(MEMORY_EXTRA);
+
+ if ($this->progress && !CLI_SCRIPT) {
+ echo html_writer::start_tag('ul');
+ }
+
+ $entirestart = microtime(true);
+
+ // Create courses.
+ $prevchdir = getcwd();
+ chdir($CFG->dirroot);
+ $ncourse = $this->get_last_testcourse_id();
+ foreach (self::$sitecourses as $coursesize => $ncourses) {
+ for ($i = 1; $i <= $ncourses[$this->size]; $i++) {
+ // Non language-dependant shortname.
+ $ncourse++;
+ $this->run_create_course(self::SHORTNAMEPREFIX . $ncourse, $coursesize);
+ }
+ }
+ chdir($prevchdir);
+
+ // Store last course id to return it (will be the bigger one).
+ $lastcourseid = $DB->get_field('course', 'id', array('shortname' => self::SHORTNAMEPREFIX . $ncourse));
+
+ // Log total time.
+ $this->log('sitecompleted', round(microtime(true) - $entirestart, 1));
+
+ if ($this->progress && !CLI_SCRIPT) {
+ echo html_writer::end_tag('ul');
+ }
+
+ return $lastcourseid;
+ }
+
+ /**
+ * Creates a course with the specified shortname, coursesize and the provided maketestsite options.
+ *
+ * @param string $shortname The course shortname
+ * @param int $coursesize One of the possible course sizes.
+ * @return void
+ */
+ protected function run_create_course($shortname, $coursesize) {
+
+ // We are in $CFG->dirroot.
+ $command = 'php admin/tool/generator/cli/maketestcourse.php';
+
+ $options = array(
+ '--shortname="' . $shortname . '"',
+ '--size="' . get_string('shortsize_' . $coursesize, 'tool_generator') . '"'
+ );
+
+ if (!$this->progress) {
+ $options[] = '--quiet';
+ }
+
+ // Extend options.
+ $optionstoextend = array(
+ 'fixeddataset' => 'fixeddataset',
+ 'bypasscheck' => 'bypasscheck',
+ );
+
+ // Getting an options string.
+ foreach ($optionstoextend as $attribute => $option) {
+ if (!empty($this->{$attribute})) {
+ $options[] = '--' . $option;
+ }
+ }
+ $options = implode(' ', $options);
+ if ($this->progress) {
+ system($command . ' ' . $options, $exitcode);
+ } else {
+ passthru($command . ' ' . $options, $exitcode);
+ }
+
+ if ($exitcode != 0) {
+ exit($exitcode);
+ }
+ }
+
+ /**
+ * Obtains the last unique sufix (numeric) using the test course prefix.
+ *
+ * @return int The last generated numeric value.
+ */
+ protected function get_last_testcourse_id() {
+ global $DB;
+
+ $params = array();
+ $params['shortnameprefix'] = $DB->sql_like_escape(self::SHORTNAMEPREFIX) . '%';
+ $like = $DB->sql_like('shortname', ':shortnameprefix');
+
+ if (!$testcourses = $DB->get_records_select('course', $like, $params, 'shortname DESC')) {
+ return 0;
+ }
+
+ // They come ordered by shortname DESC, so non-numeric values will be the first ones.
+ foreach ($testcourses as $testcourse) {
+ $sufix = substr($testcourse->shortname, strlen(self::SHORTNAMEPREFIX));
+ if (is_numeric($sufix)) {
+ return $sufix;
+ }
+ }
+
+ // If all sufixes are not numeric this is the fist make test site run.
+ return 0;
+ }
+
+}
'help' => false,
'shortname' => false,
'size' => false,
+ 'fixeddataset' => false,
'bypasscheck' => false,
'quiet' => false
),
Options:
--shortname Shortname of course to create (required)
--size Size of course to create XS, S, M, L, XL, or XXL (required)
+--fixeddataset Use a fixed data set instead of randomly generated data
--bypasscheck Bypasses the developer-mode check (be careful!)
--quiet Do not show any output
// Get options.
$shortname = $options['shortname'];
$sizename = $options['size'];
+$fixeddataset = $options['fixeddataset'];
// Check size.
try {
- $size = tool_generator_backend::size_for_name($sizename);
+ $size = tool_generator_course_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)) {
+if ($error = tool_generator_course_backend::check_shortname_available($shortname)) {
cli_error($error);
}
session_set_user(get_admin());
// Do backend code to generate course.
-$backend = new tool_generator_backend($shortname, $size, empty($options['quiet']));
+$backend = new tool_generator_course_backend($shortname, $size, $fixeddataset, empty($options['quiet']));
$id = $backend->make();
--- /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 site.
+ *
+ * @package tool_generator
+ * @copyright 2013 David Monllaó
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+define('CLI_SCRIPT', true);
+define('NO_OUTPUT_BUFFERING', true);
+
+require(__DIR__ . '/../../../../config.php');
+require_once($CFG->libdir. '/clilib.php');
+
+// CLI options.
+list($options, $unrecognized) = cli_get_params(
+ array(
+ 'help' => false,
+ 'size' => false,
+ 'fixeddataset' => false,
+ 'bypasscheck' => false,
+ 'quiet' => false
+ ),
+ array(
+ 'h' => 'help'
+ )
+);
+
+$sitesizes = '* ' . implode(PHP_EOL . '* ', tool_generator_site_backend::get_size_choices());
+
+// Display help.
+if (!empty($options['help']) || empty($options['size'])) {
+ echo "
+Utility to generate a standard test site data set.
+
+Not for use on live sites; only normally works if debugging is set to DEVELOPER
+level.
+
+Consider that, depending on the size you select, this CLI tool can really generate a lot of data, aproximated sizes:
+
+$sitesizes
+
+Options:
+--size Size of the generated site, this value affects the number of courses and their size. Accepted values: XS, S, M, L, XL, or XXL (required)
+--fixeddataset Use a fixed data set instead of randomly generated data
+--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/maketestsite.php --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']) && !$CFG->debugdeveloper) {
+ cli_error(get_string('error_notdebugging', 'tool_generator'));
+}
+
+// Get options.
+$sizename = $options['size'];
+$fixeddataset = $options['fixeddataset'];
+
+// Check size.
+try {
+ $size = tool_generator_site_backend::size_for_name($sizename);
+} catch (coding_exception $e) {
+ cli_error("Invalid size ($sizename). Use --help for help.");
+}
+
+// Switch to admin user account.
+session_set_user(get_admin());
+
+// Do backend code to generate site.
+$backend = new tool_generator_site_backend($size, $options['bypasscheck'], $fixeddataset, empty($options['quiet']));
+$backend->make();
*/
$string['bigfile'] = 'Big file {$a}';
+$string['coursesize_0'] = 'XS (~10KB; create in ~1 second)';
+$string['coursesize_1'] = 'S (~10MB; create in ~30 seconds)';
+$string['coursesize_2'] = 'M (~100MB; create in ~5 minutes)';
+$string['coursesize_3'] = 'L (~1GB; create in ~1 hour)';
+$string['coursesize_4'] = 'XL (~10GB; create in ~4 hours)';
+$string['coursesize_5'] = 'XXL (~20GB; create in ~8 hours)';
$string['createcourse'] = 'Create course';
$string['creating'] = 'Creating course';
$string['done'] = 'done ({$a}s)';
$string['firstname'] = 'Test course user';
$string['fullname'] = 'Test course: {$a->size}';
$string['maketestcourse'] = 'Make test course';
-$string['pluginname'] = 'Random course generator';
+$string['pluginname'] = 'Development data generator';
$string['progress_createcourse'] = 'Creating course {$a}';
$string['progress_checkaccounts'] = 'Checking user accounts ({$a})';
+$string['progress_coursecompleted'] = 'Course completed ({$a}s)';
$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['progress_sitecompleted'] = 'Site completed ({$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['sitesize_0'] = 'XS (~10MB; 3 courses, created in ~30 seconds)';
+$string['sitesize_1'] = 'S (~50MB; 8 courses, created in ~2 minutes)';
+$string['sitesize_2'] = 'M (~200MB; 73 courses, created in ~10 minutes)';
+$string['sitesize_3'] = 'L (~1\'5GB; 277 courses, created in ~1\'5 hours)';
+$string['sitesize_4'] = 'XL (~10GB; 1065 courses, created in ~5 hours)';
+$string['sitesize_5'] = 'XXL (~20GB; 4177 courses, created in ~10 hours)';
$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';
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);
+ $backend = new tool_generator_course_backend($data->shortname, $data->size);
$id = $backend->make();
echo html_writer::div(
$this->setAdminUser();
// Create the XS course.
- $backend = new tool_generator_backend('TOOL_MAKELARGECOURSE_XS', 0, false);
+ $backend = new tool_generator_course_backend('TOOL_MAKELARGECOURSE_XS', 0, false, false);
$courseid = $backend->make();
// Get course details.
fd.forum = ?", array($forum->instance));
$this->assertEquals(2, $posts);
}
+
+ /**
+ * Creates an small test course with fixed data set and checks the used sections and users.
+ */
+ public function test_fixed_data_set() {
+ global $DB;
+
+ $this->resetAfterTest();
+ $this->setAdminUser();
+
+ // Create the S course (more sections and activities than XS).
+ $backend = new tool_generator_course_backend('TOOL_S_COURSE_1', 1, true, false);
+ $courseid = $backend->make();
+
+ // Get course details.
+ $course = get_course($courseid);
+ $modinfo = get_fast_modinfo($course);
+
+ // Check module instances belongs to section 1.
+ $instances = $modinfo->get_instances_of('page');
+ $npageinstances = count($instances);
+ foreach ($instances as $instance) {
+ $this->assertEquals(1, $instance->sectionnum);
+ }
+
+ // Users that started discussions are the same.
+ $forums = $modinfo->get_instances_of('forum');
+ $nforuminstances = count($forums);
+ $discussions = forum_get_discussions(reset($forums), 'd.timemodified ASC');
+ $lastusernumber = 0;
+ $discussionstarters = array();
+ foreach ($discussions as $discussion) {
+ $usernumber = intval($discussion->lastname);
+
+ // Checks that the users are odd numbers.
+ $this->assertEquals(1, $usernumber % 2);
+
+ // Checks that the users follows an increasing order.
+ $this->assertGreaterThan($lastusernumber, $usernumber);
+ $lastusernumber = $usernumber;
+ $discussionstarters[$discussion->userid] = $discussion->subject;
+ }
+
+ }
}
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2013080700;
-$plugin->requires = 2013080200;
+$plugin->version = 2013090200;
+$plugin->requires = 2013090200;
$plugin->component = 'tool_generator';
require_once($CFG->libdir.'/adminlib.php');
require_once($CFG->libdir.'/csvlib.class.php');
require_once($CFG->dirroot.'/user/profile/lib.php');
+require_once($CFG->dirroot.'/user/lib.php');
require_once($CFG->dirroot.'/group/lib.php');
require_once($CFG->dirroot.'/cohort/lib.php');
require_once('locallib.php');
}
if ($doupdate or $existinguser->password !== $oldpw) {
- // we want only users that were really updated
-
- $DB->update_record('user', $existinguser);
+ // We want only users that were really updated.
+ user_update_user($existinguser, false);
$upt->track('status', $struserupdated);
$usersupdated++;
profile_save_data($existinguser);
}
- events_trigger('user_updated', $existinguser);
-
if ($bulk == UU_BULK_UPDATED or $bulk == UU_BULK_ALL) {
if (!in_array($user->id, $SESSION->bulk_users)) {
$SESSION->bulk_users[] = $user->id;
$upt->track('password', '-', 'normal', false);
}
- // create user - insert_record ignores any extra properties
- $user->id = $DB->insert_record('user', $user);
+ $user->id = user_create_user($user, false);
$upt->track('username', html_writer::link(new moodle_url('/user/profile.php', array('id'=>$user->id)), s($user->username)), 'normal', false);
// pre-process custom profile menu fields data from csv file
// make sure user context exists
context_user::instance($user->id);
- events_trigger('user_created', $user);
-
if ($bulk == UU_BULK_NEW or $bulk == UU_BULK_ALL) {
if (!in_array($user->id, $SESSION->bulk_users)) {
$SESSION->bulk_users[] = $user->id;
require_once($CFG->libdir.'/adminlib.php');
require_once($CFG->libdir.'/authlib.php');
require_once($CFG->dirroot.'/user/filters/lib.php');
+ require_once($CFG->dirroot.'/user/lib.php');
$delete = optional_param('delete', 0, PARAM_INT);
$confirm = optional_param('confirm', '', PARAM_ALPHANUM); //md5 confirmation hash
if ($user = $DB->get_record('user', array('id'=>$suspend, 'mnethostid'=>$CFG->mnet_localhost_id, 'deleted'=>0))) {
if (!is_siteadmin($user) and $USER->id != $user->id and $user->suspended != 1) {
$user->suspended = 1;
- $user->timemodified = time();
- $DB->set_field('user', 'suspended', $user->suspended, array('id'=>$user->id));
- $DB->set_field('user', 'timemodified', $user->timemodified, array('id'=>$user->id));
- // force logout
+ // Force logout.
session_kill_user($user->id);
- events_trigger('user_updated', $user);
+ user_update_user($user, false);
}
}
redirect($returnurl);
if ($user = $DB->get_record('user', array('id'=>$unsuspend, 'mnethostid'=>$CFG->mnet_localhost_id, 'deleted'=>0))) {
if ($user->suspended != 0) {
$user->suspended = 0;
- $user->timemodified = time();
- $DB->set_field('user', 'suspended', $user->suspended, array('id'=>$user->id));
- $DB->set_field('user', 'timemodified', $user->timemodified, array('id'=>$user->id));
- events_trigger('user_updated', $user);
+ user_update_user($user, false);
}
}
redirect($returnurl);
$remove_users = $DB->get_records_sql($sql, $params);
if (!empty($remove_users)) {
+ require_once($CFG->dirroot.'/user/lib.php');
$trace->output(get_string('auth_dbuserstoremove','auth_db', count($remove_users)));
foreach ($remove_users as $user) {
$updateuser = new stdClass();
$updateuser->id = $user->id;
$updateuser->suspended = 1;
- $updateuser->timemodified = time();
- $DB->update_record('user', $updateuser);
+ user_update_user($updateuser, false);
$trace->output(get_string('auth_dbsuspenduser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id)), 1);
}
}
function user_signup($user, $notify=true) {
global $CFG, $DB;
require_once($CFG->dirroot.'/user/profile/lib.php');
+ require_once($CFG->dirroot.'/user/lib.php');
$user->password = hash_internal_user_password($user->password);
- $user->id = $DB->insert_record('user', $user);
+ $user->id = user_create_user($user, false);
- /// Save any custom profile field information
+ // Save any custom profile field information.
profile_save_data($user);
- $user = $DB->get_record('user', array('id'=>$user->id));
- events_trigger('user_created', $user);
-
if (! send_confirmation_email($user)) {
print_error('auth_emailnoemail','auth_email');
}
require_once($CFG->libdir.'/authlib.php');
require_once($CFG->libdir.'/ldaplib.php');
+require_once($CFG->dirroot.'/user/lib.php');
/**
* LDAP authentication plugin.
print_error('auth_ldap_create_error', 'auth_ldap');
}
- $user->id = $DB->insert_record('user', $user);
+ $user->id = user_create_user($user, false);
// Save any custom profile field information
profile_save_data($user);
update_internal_user_password($user, $plainslashedpassword);
$user = $DB->get_record('user', array('id'=>$user->id));
- events_trigger('user_created', $user);
if (! send_confirmation_email($user)) {
print_error('noemail', 'auth_ldap');
if (!$this->user_activate($username)) {
return AUTH_CONFIRM_FAIL;
}
- $DB->set_field('user', 'confirmed', 1, array('id'=>$user->id));
+ $user->confirmed = 1;
if ($user->firstaccess == 0) {
- $DB->set_field('user', 'firstaccess', time(), array('id'=>$user->id));
+ $user->firstaccess = time();
}
- $euser = $DB->get_record('user', array('id' => $user->id));
- events_trigger('user_updated', $euser);
+ require_once($CFG->dirroot.'/user/lib.php');
+ user_update_user($user, false);
return AUTH_CONFIRM_OK;
}
} else {
$updateuser = new stdClass();
$updateuser->id = $user->id;
$updateuser->suspended = 1;
- $DB->update_record('user', $updateuser);
+ user_update_user($updateuser, false);
echo "\t"; print_string('auth_dbsuspenduser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id)); echo "\n";
- $euser = $DB->get_record('user', array('id' => $user->id));
- events_trigger('user_updated', $euser);
session_kill_user($user->id);
}
} else {
$updateuser->id = $user->id;
$updateuser->auth = $this->authtype;
$updateuser->suspended = 0;
- $DB->update_record('user', $updateuser);
+ user_update_user($updateuser, false);
echo "\t"; print_string('auth_dbreviveduser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id)); echo "\n";
- $euser = $DB->get_record('user', array('id' => $user->id));
- events_trigger('user_updated', $euser);
}
} else {
print_string('nouserentriestorevive', 'auth_ldap');
$user->lang = $CFG->lang;
}
- $id = $DB->insert_record('user', $user);
+ $id = user_create_user($user, false);
echo "\t"; print_string('auth_dbinsertuser', 'auth_db', array('name'=>$user->username, 'id'=>$id)); echo "\n";
$euser = $DB->get_record('user', array('id' => $id));
- events_trigger('user_created', $euser);
+
if (!empty($this->config->forcechangepassword)) {
set_user_preference('auth_forcepasswordchange', 1, $id);
}
$updatekeys = array_keys($newinfo);
}
- foreach ($updatekeys as $key) {
- if (isset($newinfo[$key])) {
- $value = $newinfo[$key];
- } else {
- $value = '';
- }
+ if (!empty($updatekeys)) {
+ $newuser = new stdClass();
+ $newuser->id = $userid;
+
+ foreach ($updatekeys as $key) {
+ if (isset($newinfo[$key])) {
+ $value = $newinfo[$key];
+ } else {
+ $value = '';
+ }
- if (!empty($this->config->{'field_updatelocal_' . $key})) {
- if ($user->{$key} != $value) { // only update if it's changed
- $DB->set_field('user', $key, $value, array('id'=>$userid));
+ if (!empty($this->config->{'field_updatelocal_' . $key})) {
+ // Only update if it's changed.
+ if ($user->{$key} != $value) {
+ $newuser->$key = $value;
+ }
}
}
- }
- if (!empty($updatekeys)) {
- $euser = $DB->get_record('user', array('id' => $userid));
- events_trigger('user_updated', $euser);
+ user_update_user($newuser, false);
}
} else {
return false;
global $CFG, $DB;
require_once $CFG->dirroot . '/mnet/xmlrpc/client.php';
require_once $CFG->libdir . '/gdlib.php';
+ require_once($CFG->dirroot.'/user/lib.php');
// verify the remote host is configured locally before attempting RPC call
if (! $remotehost = $DB->get_record('mnet_host', array('wwwroot' => $remotepeer->wwwroot, 'deleted' => 0))) {
if (empty($localuser->firstaccess)) { // Now firstaccess, grab it here
$localuser->firstaccess = time();
}
-
- $DB->update_record('user', $localuser);
+ user_update_user($localuser, false);
if (!$firsttime) {
// repeat customer! let the IDP know about enrolments
}
$backup = new backup_ui($bc);
$backup->process();
-if ($backup->get_stage() == backup_ui::STAGE_FINAL) {
- $backup->execute();
-} else {
- $backup->save_controller();
-}
$PAGE->set_title($heading.': '.$backup->get_stage_name());
$PAGE->set_heading($heading);
if ($backup->enforce_changed_dependencies()) {
debugging('Your settings have been altered due to unmet dependencies', DEBUG_DEVELOPER);
}
+
+if ($backup->get_stage() == backup_ui::STAGE_FINAL) {
+ // Display an extra progress bar so that we can show the progress first.
+ echo html_writer::start_div('', array('id' => 'executionprogress'));
+ echo $renderer->progress_bar($backup->get_progress_bar());
+ $backup->get_controller()->set_progress(new core_backup_display_progress());
+ $backup->execute();
+ echo html_writer::end_div();
+ echo html_writer::script('document.getElementById("executionprogress").style.display = "none";');
+} else {
+ $backup->save_controller();
+}
+
echo $renderer->progress_bar($backup->get_progress_bar());
echo $backup->display($renderer);
$backup->destroy();
protected $destination; // Destination chain object (fs_moodle, fs_os, db, email...)
protected $logger; // Logging chain object (moodle, inline, fs, db, syslog)
+ /**
+ * @var core_backup_progress Progress reporting object.
+ */
+ protected $progress;
+
protected $checksum; // Cache @checksumable results for lighter @is_checksum_correct() uses
/**
// Default logger chain (based on interactive/execution)
$this->logger = backup_factory::get_logger_chain($this->interactive, $this->execution, $this->backupid);
+ // By default there is no progress reporter. Interfaces that wish to
+ // display progress must set it.
+ $this->progress = new core_backup_null_progress();
+
// Instantiate the output_controller singleton and active it if interactive and inmediate
$oc = output_controller::get_instance();
if ($this->interactive == backup::INTERACTIVE_YES && $this->execution == backup::EXECUTION_INMEDIATE) {
return $this->logger;
}
+ /**
+ * Gets the progress reporter, which can be used to report progress within
+ * the backup or restore process.
+ *
+ * @return core_backup_progress Progress reporting object
+ */
+ public function get_progress() {
+ return $this->progress;
+ }
+
+ /**
+ * Sets the progress reporter.
+ *
+ * @param core_backup_progress $progress Progress reporting object
+ */
+ public function set_progress(core_backup_progress $progress) {
+ $this->progress = $progress;
+ }
+
/**
* Executes the backup
* @return void Throws and exception of completes
protected $logger; // Logging chain object (moodle, inline, fs, db, syslog)
+ /**
+ * @var core_backup_progress Progress reporting object.
+ */
+ protected $progress;
+
protected $checksum; // Cache @checksumable results for lighter @is_checksum_correct() uses
/**
// Default logger chain (based on interactive/execution)
$this->logger = backup_factory::get_logger_chain($this->interactive, $this->execution, $this->restoreid);
+ // By default there is no progress reporter. Interfaces that wish to
+ // display progress must set it.
+ $this->progress = new core_backup_null_progress();
+
// Instantiate the output_controller singleton and active it if interactive and inmediate
$oc = output_controller::get_instance();
if ($this->interactive == backup::INTERACTIVE_YES && $this->execution == backup::EXECUTION_INMEDIATE) {
return $this->logger;
}
+ /**
+ * Gets the progress reporter, which can be used to report progress within
+ * the backup or restore process.
+ *
+ * @return core_backup_progress Progress reporting object
+ */
+ public function get_progress() {
+ return $this->progress;
+ }
+
+ /**
+ * Sets the progress reporter.
+ *
+ * @param core_backup_progress $progress Progress reporting object
+ */
+ public function set_progress(core_backup_progress $progress) {
+ $this->progress = $progress;
+ }
+
public function execute_plan() {
// Basic/initial prevention against time/memory limits
set_time_limit(1 * 60 * 60); // 1 hour for 1 course initially granted
// If it's the final stage process the import
if ($backup->get_stage() == backup_ui::STAGE_FINAL) {
+ echo $OUTPUT->header();
+
+ // Display an extra progress bar so that we can show the current stage.
+ echo html_writer::start_div('', array('id' => 'executionprogress'));
+ echo $renderer->progress_bar($backup->get_progress_bar());
+
+ // Start the progress display - we split into 2 chunks for backup and restore.
+ $progress = new core_backup_display_progress();
+ $progress->start_progress('', 2);
+ $backup->get_controller()->set_progress($progress);
+
// First execute the backup
$backup->execute();
$backup->destroy();
unset($backup);
+ // Note that we've done that progress.
+ $progress->progress(1);
+
// Check whether the backup directory still exists. If missing, something
// went really wrong in backup, throw error. Note that backup::MODE_IMPORT
// backups don't store resulting files ever
// Prepare the restore controller. We don't need a UI here as we will just use what
// ever the restore has (the user has just chosen).
$rc = new restore_controller($backupid, $course->id, backup::INTERACTIVE_YES, backup::MODE_IMPORT, $USER->id, $restoretarget);
+ $rc->set_progress($progress);
// Convert the backup if required.... it should NEVER happed
if ($rc->get_status() == backup::STATUS_REQUIRE_CONV) {
$rc->convert();
// Delete the temp directory now
fulldelete($tempdestination);
+ // All progress complete. Hide progress area.
+ $progress->end_progress();
+ echo html_writer::end_div();
+ echo html_writer::script('document.getElementById("executionprogress").style.display = "none";');
+
// Display a notification and a continue button
- echo $OUTPUT->header();
if ($warnings) {
echo $OUTPUT->box_start();
echo $OUTPUT->notification(get_string('warning'), 'notifywarning');
$this->built = true;
}
+ public function get_weight() {
+ // The final task takes ages, so give it 20 times the weight of a normal task.
+ return 20;
+ }
+
// Protected API starts here
/**
}
$outcome = $restore->process();
+$heading = $course->fullname;
+
+$PAGE->set_title($heading.': '.$restore->get_stage_name());
+$PAGE->set_heading($heading);
+$PAGE->navbar->add($restore->get_stage_name());
+
+$renderer = $PAGE->get_renderer('core','backup');
+echo $OUTPUT->header();
+if (!$restore->is_independent() && $restore->enforce_changed_dependencies()) {
+ debugging('Your settings have been altered due to unmet dependencies', DEBUG_DEVELOPER);
+}
+
if (!$restore->is_independent()) {
if ($restore->get_stage() == restore_ui::STAGE_PROCESS && !$restore->requires_substage()) {
try {
+ // Display an extra progress bar so that we can show the progress first.
+ echo html_writer::start_div('', array('id' => 'executionprogress'));
+ echo $renderer->progress_bar($restore->get_progress_bar());
+ $restore->get_controller()->set_progress(new core_backup_display_progress());
$restore->execute();
+ echo html_writer::end_div();
+ echo html_writer::script('document.getElementById("executionprogress").style.display = "none";');
} catch(Exception $e) {
$restore->cleanup();
throw $e;
$restore->save_controller();
}
}
-$heading = $course->fullname;
-
-$PAGE->set_title($heading.': '.$restore->get_stage_name());
-$PAGE->set_heading($heading);
-$PAGE->navbar->add($restore->get_stage_name());
-$renderer = $PAGE->get_renderer('core','backup');
-echo $OUTPUT->header();
-if (!$restore->is_independent() && $restore->enforce_changed_dependencies()) {
- debugging('Your settings have been altered due to unmet dependencies', DEBUG_DEVELOPER);
-}
echo $renderer->progress_bar($restore->get_progress_bar());
echo $restore->display($renderer);
$restore->destroy();
require_once($CFG->dirroot . '/backup/util/loggers/file_logger.class.php');
require_once($CFG->dirroot . '/backup/util/loggers/database_logger.class.php');
require_once($CFG->dirroot . '/backup/util/loggers/output_indented_logger.class.php');
+require_once($CFG->dirroot . '/backup/util/progress/core_backup_progress.class.php');
+require_once($CFG->dirroot . '/backup/util/progress/core_backup_null_progress.class.php');
+require_once($CFG->dirroot . '/backup/util/progress/core_backup_display_progress.class.php');
require_once($CFG->dirroot . '/backup/util/settings/setting_dependency.class.php');
require_once($CFG->dirroot . '/backup/util/settings/base_setting.class.php');
require_once($CFG->dirroot . '/backup/util/settings/backup_setting.class.php');
require_once($CFG->dirroot . '/backup/util/loggers/file_logger.class.php');
require_once($CFG->dirroot . '/backup/util/loggers/database_logger.class.php');
require_once($CFG->dirroot . '/backup/util/loggers/output_indented_logger.class.php');
+require_once($CFG->dirroot . '/backup/util/progress/core_backup_progress.class.php');
+require_once($CFG->dirroot . '/backup/util/progress/core_backup_null_progress.class.php');
+require_once($CFG->dirroot . '/backup/util/progress/core_backup_display_progress.class.php');
require_once($CFG->dirroot . '/backup/util/factories/backup_factory.class.php');
require_once($CFG->dirroot . '/backup/util/factories/restore_factory.class.php');
require_once($CFG->dirroot . '/backup/util/helper/backup_helper.class.php');
return $this->controller->get_logger();
}
+ /**
+ * Gets the progress reporter, which can be used to report progress within
+ * the backup or restore process.
+ *
+ * @return core_backup_progress Progress reporting object
+ */
+ public function get_progress() {
+ return $this->controller->get_progress();
+ }
+
public function is_excluding_activities() {
return $this->excludingdactivities;
}
if (!$this->built) {
throw new base_plan_exception('base_plan_not_built');
}
+
+ // Calculate the total weight of all tasks and start progress tracking.
+ $progress = $this->get_progress();
+ $totalweight = 0;
+ foreach ($this->tasks as $task) {
+ $totalweight += $task->get_weight();
+ }
+ $progress->start_progress($this->get_name(), $totalweight);
+
+ // Build and execute all tasks.
foreach ($this->tasks as $task) {
$task->build();
$task->execute();
}
+
+ // Finish progress tracking.
+ $progress->end_progress();
}
+ /**
+ * Gets the progress reporter, which can be used to report progress within
+ * the backup or restore process.
+ *
+ * @return core_backup_progress Progress reporting object
+ */
+ public abstract function get_progress();
+
/**
* Destroy all circular references. It helps PHP 5.2 a lot!
*/
return $this->settings;
}
+ /**
+ * Returns the weight of this task, an approximation of the amount of time
+ * it will take. By default this value is 1. It can be increased for longer
+ * tasks.
+ *
+ * @return int Weight
+ */
+ public function get_weight() {
+ return 1;
+ }
+
public function get_setting($name) {
// First look in task settings
$result = null;
return $this->plan->get_logger();
}
+ /**
+ * Gets the progress reporter, which can be used to report progress within
+ * the backup or restore process.
+ *
+ * @return core_backup_progress Progress reporting object
+ */
+ public function get_progress() {
+ return $this->plan->get_progress();
+ }
+
public function log($message, $level, $a = null, $depth = null, $display = false) {
backup_helper::log($message, $level, $a, $depth, $display, $this->get_logger());
}
if ($this->executed) {
throw new base_task_exception('base_task_already_executed', $this->name);
}
+
+ // Starts progress based on the weight of this task and number of steps.
+ $progress = $this->get_progress();
+ $progress->start_progress($this->get_name(), count($this->steps), $this->get_weight());
+ $done = 0;
+
+ // Execute all steps.
foreach ($this->steps as $step) {
$result = $step->execute();
// If step returns array, it will be forwarded to plan
if (is_array($result) and !empty($result)) {
$this->add_result($result);
}
+ $done++;
+ $progress->progress($done);
}
// Mark as executed if any step has been executed
if (!empty($this->steps)) {
$this->executed = true;
}
+
+ // Finish progress for this task.
+ $progress->end_progress();
}
/**
return $this->controller->get_logger();
}
+ /**
+ * Gets the progress reporter, which can be used to report progress within
+ * the backup or restore process.
+ *
+ * @return core_backup_progress Progress reporting object
+ */
+ public function get_progress() {
+ return $this->controller->get_progress();
+ }
+
public function get_info() {
return $this->controller->get_info();
}
class mock_base_plan extends base_plan {
public function build() {
}
+
+ public function get_progress() {
+ return null;
+ }
}
/**
--- /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/>.
+
+/**
+ * Progress handler that uses a standard Moodle progress bar to display
+ * progress. The Moodle progress bar cannot show indeterminate progress,
+ * so we do extra output in addition to the bar.
+ *
+ * @package core_backup
+ * @copyright 2013 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_backup_display_progress extends core_backup_progress {
+ /**
+ * @var int Number of wibble states (state0...stateN-1 classes in CSS)
+ */
+ const WIBBLE_STATES = 13;
+
+ /**
+ * @var progress_bar Current progress bar.
+ */
+ private $bar;
+
+ private $lastwibble, $currentstate = 0, $direction = 1;
+
+ /**
+ * @var bool True to display names
+ */
+ protected $displaynames = false;
+
+ /**
+ * Constructs the progress reporter. This will output HTML code for the
+ * progress bar, and an indeterminate wibbler below it.
+ *
+ * @param bool $startnow If true, outputs HTML immediately.
+ */
+ public function __construct($startnow = true) {
+ if ($startnow) {
+ $this->start_html();
+ }
+ }
+
+ /**
+ * By default, the progress section names do not display because (in backup)
+ * these are usually untranslated and incomprehensible. To make them
+ * display, call this method.
+ *
+ * @param bool $displaynames True to display names
+ */
+ public function set_display_names($displaynames = true) {
+ $this->displaynames = $displaynames;
+ }
+
+ /**
+ * Starts to output progress.
+ *
+ * Called in constructor and in update_progress if required.
+ *
+ * @throws coding_exception If already started
+ */
+ public function start_html() {
+ if ($this->bar) {
+ throw new coding_exception('Already started');
+ }
+ $this->bar = new progress_bar();
+ $this->bar->create();
+ echo html_writer::start_div('wibbler');
+ }
+
+ /**
+ * Finishes output. (Progress can begin again later if there are more
+ * calls to update_progress.)
+ *
+ * Automatically called from update_progress when progress finishes.
+ */
+ public function end_html() {
+ // Finish progress bar.
+ $this->bar->update_full(100, '');
+ $this->bar = null;
+
+ // End wibbler div.
+ echo html_writer::end_div();
+ }
+
+ public function update_progress() {
+ // If finished...
+ if (!$this->is_in_progress_section()) {
+ if ($this->bar) {
+ $this->end_html();
+ }
+ } else {
+ if (!$this->bar) {
+ $this->start_html();
+ }
+ // In case of indeterminate or small progress, update the wibbler
+ // (up to once per second).
+ if (time() != $this->lastwibble) {
+ $this->lastwibble = time();
+ echo html_writer::div('', 'wibble state' . $this->currentstate);
+
+ // Go on to next colour.
+ $this->currentstate += $this->direction;
+ if ($this->currentstate < 0 || $this->currentstate >= self::WIBBLE_STATES) {
+ $this->direction = -$this->direction;
+ $this->currentstate += 2 * $this->direction;
+ }
+ }
+
+ // Get progress.
+ list ($min, $max) = $this->get_progress_proportion_range();
+
+ // Update progress bar.
+ $message = '';
+ if ($this->displaynames) {
+ $message = $this->get_current_description();
+ }
+ $this->bar->update_full($min * 100, $message);
+
+ // Flush output.
+ flush();
+ }
+ }
+}
--- /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/>.
+
+/**
+ * Progress handler that ignores progress entirely.
+ *
+ * @package core_backup
+ * @copyright 2013 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_backup_null_progress extends core_backup_progress {
+ public function update_progress() {
+ // Do nothing.
+ }
+}
--- /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/>.
+
+/**
+ * Base class for handling progress information during a backup and restore.
+ *
+ * Subclasses should generally override the current_progress function which
+ * summarises all progress information.
+ *
+ * @package core_backup
+ * @copyright 2013 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+abstract class core_backup_progress {
+ /**
+ * @var int Constant indicating that the number of progress calls is unknown.
+ */
+ const INDETERMINATE = -1;
+
+ /**
+ * @var int The number of seconds that can pass without progress() calls.
+ */
+ const TIME_LIMIT_WITHOUT_PROGRESS = 120;
+
+ /**
+ * @var int Time of last progress call.
+ */
+ protected $lastprogresstime;
+
+ /**
+ * @var int Number of progress calls (restricted to ~ 1/second).
+ */
+ protected $count;
+
+ /**
+ * @var array Array of progress descriptions for each stack level.
+ */
+ protected $descriptions = array();
+
+ /**
+ * @var array Array of maximum progress values for each stack level.
+ */
+ protected $maxes = array();
+
+ /**
+ * @var array Array of current progress values.
+ */
+ protected $currents = array();
+
+ /**
+ * @var int Array of counts within parent progress entry (ignored for first)
+ */
+ protected $parentcounts = array();
+
+ /**
+ * Marks the start of an operation that will display progress.
+ *
+ * This can be called multiple times for nested progress sections. It must
+ * be paired with calls to end_progress.
+ *
+ * The progress maximum may be INDETERMINATE if the current operation has
+ * an unknown number of steps. (This is default.)
+ *
+ * Calling this function will always result in a new display, so this
+ * should not be called exceedingly frequently.
+ *
+ * When it is complete by calling end_progress, each start_progress section
+ * automatically adds progress to its parent, as defined by $parentcount.
+ *
+ * @param string $description Description to display
+ * @param int $max Maximum value of progress for this section
+ * @param int $parentcount How many progress points this section counts for
+ * @throws coding_exception If max is invalid
+ */
+ public function start_progress($description, $max = self::INDETERMINATE,
+ $parentcount = 1) {
+ if ($max != self::INDETERMINATE && $max <= 0) {
+ throw new coding_exception(
+ 'start_progress() max value cannot be zero or negative');
+ }
+ if ($parentcount < 1) {
+ throw new coding_exception(
+ 'start_progress() parent progress count must be at least 1');
+ }
+ if (!empty($this->descriptions)) {
+ $prevmax = end($this->maxes);
+ if ($prevmax !== self::INDETERMINATE) {
+ $prevcurrent = end($this->currents);
+ if ($prevcurrent + $parentcount > $prevmax) {
+ throw new coding_exception(
+ 'start_progress() parent progress would exceed max');
+ }
+ }
+ } else {
+ if ($parentcount != 1) {
+ throw new coding_exception(
+ 'start_progress() progress count must be 1 when no parent');
+ }
+ }
+ $this->descriptions[] = $description;
+ $this->maxes[] = $max;
+ $this->currents[] = 0;
+ $this->parentcounts[] = $parentcount;
+ $this->update_progress();
+ $lastprogresstime = $this->get_time();
+ }
+
+ /**
+ * Marks the end of an operation that will display progress.
+ *
+ * This must be paired with each start_progress call.
+ *
+ * If there is a parent progress section, its progress will be increased
+ * automatically to reflect the end of the child section.
+ *
+ * @throws coding_exception If progress hasn't been started
+ */
+ public function end_progress() {
+ if (!count($this->descriptions)) {
+ throw new coding_exception('end_progress() without start_progress()');
+ }
+ array_pop($this->descriptions);
+ array_pop($this->maxes);
+ array_pop($this->currents);
+ $parentcount = array_pop($this->parentcounts);
+ if (!empty($this->descriptions)) {
+ $lastmax = end($this->maxes);
+ if ($lastmax != self::INDETERMINATE) {
+ $lastvalue = end($this->currents);
+ $this->currents[key($this->currents)] = $lastvalue + $parentcount;
+ }
+ }
+ $this->update_progress();
+ }
+
+ /**
+ * Indicates that progress has occurred.
+ *
+ * The progress value should indicate the total progress so far, from 0
+ * to the value supplied for $max (inclusive) in start_progress.
+ *
+ * You do not need to call this function for every value. It is OK to skip
+ * values. It is also OK to call this function as often as desired; it
+ * doesn't do anything if called more than once per second.
+ *
+ * It must be INDETERMINATE if start_progress was called with $max set to
+ * INDETERMINATE. Otherwise it must not be indeterminate.
+ *
+ * @param int $progress Progress so far
+ * @throws coding_exception If progress value is invalid
+ */
+ public function progress($progress = self::INDETERMINATE) {
+ // Ignore too-frequent progress calls (more than once per second).
+ $now = $this->get_time();
+ if ($now === $this->lastprogresstime) {
+ return;
+ }
+
+ // Check we are inside a progress section.
+ $max = end($this->maxes);
+ if ($max === false) {
+ throw new coding_exception(
+ 'progress() without start_progress');
+ }
+
+ // Check and apply new progress.
+ if ($progress === self::INDETERMINATE) {
+ // Indeterminate progress.
+ if ($max !== self::INDETERMINATE) {
+ throw new coding_exception(
+ 'progress() INDETERMINATE, expecting value');
+ }
+ } else {
+ // Determinate progress.
+ $current = end($this->currents);
+ if ($max === self::INDETERMINATE) {
+ throw new coding_exception(
+ 'progress() with value, expecting INDETERMINATE');
+ } else if ($progress < 0 || $progress > $max) {
+ throw new coding_exception(
+ 'progress() value out of range');
+ } else if ($progress < $current) {
+ throw new coding_Exception(
+ 'progress() value may not go backwards');
+ }
+ $this->currents[key($this->currents)] = $progress;
+ }
+
+ // Update progress.
+ $this->count++;
+ $this->lastprogresstime = $now;
+ set_time_limit(self::TIME_LIMIT_WITHOUT_PROGRESS);
+ $this->update_progress();
+ }
+
+ /**
+ * Gets time (this is provided so that unit tests can override it).
+ *
+ * @return int Current system time
+ */
+ protected function get_time() {
+ return time();
+ }
+
+ /**
+ * Called whenever new progress should be displayed.
+ */
+ protected abstract function update_progress();
+
+ /**
+ * @return bool True if currently inside a progress section
+ */
+ public function is_in_progress_section() {
+ return !empty($this->descriptions);
+ }
+
+ /**
+ * @return string Current progress section description
+ */
+ public function get_current_description() {
+ $description = end($this->descriptions);
+ if ($description === false) {
+ throw new coding_exception('Not inside progress section');
+ }
+ return $description;
+ }
+
+ /**
+ * Obtains current progress in a way suitable for drawing a progress bar.
+ *
+ * Progress is returned as a minimum and maximum value. If there is no
+ * indeterminate progress, these values will be identical. If there is
+ * intermediate progress, these values can be different. (For example, if
+ * the top level progress sections is indeterminate, then the values will
+ * always be 0.0 and 1.0.)
+ *
+ * @return array Minimum and maximum possible progress proportions
+ */
+ public function get_progress_proportion_range() {
+ // If there is no progress underway, we must have finished.
+ if (empty($this->currents)) {
+ return array(1.0, 1.0);
+ }
+ $count = count($this->currents);
+ $min = 0.0;
+ $max = 1.0;
+ for ($i = 0; $i < $count; $i++) {
+ // Get max value at that section - if it's indeterminate we can tell
+ // no more.
+ $sectionmax = $this->maxes[$i];
+ if ($sectionmax === self::INDETERMINATE) {
+ return array($min, $max);
+ }
+
+ // Special case if current value is max (this should only happen
+ // just before ending a section).
+ $sectioncurrent = $this->currents[$i];
+ if ($sectioncurrent === $sectionmax) {
+ return array($max, $max);
+ }
+
+ // Using the current value at that section, we know we are somewhere
+ // between 'current' and the next 'current' value which depends on
+ // the parentcount of the nested section (if any).
+ $newmin = ($sectioncurrent / $sectionmax) * ($max - $min) + $min;
+ $nextcurrent = $sectioncurrent + 1;
+ if ($i + 1 < $count) {
+ $weight = $this->parentcounts[$i + 1];
+ $nextcurrent = $sectioncurrent + $weight;
+ }
+ $newmax = ($nextcurrent / $sectionmax) * ($max - $min) + $min;
+ $min = $newmin;
+ $max = $newmax;
+ }
+
+ // If there was nothing indeterminate, we use the min value as current.
+ return array($min, $min);
+ }
+
+ /**
+ * Obtains current indeterminate progress in a way suitable for adding to
+ * the progress display.
+ *
+ * This returns the number of indeterminate calls (at any level) during the
+ * lifetime of this progress reporter, whether or not there is a current
+ * indeterminate step. (The number will not be ridiculously high because
+ * progress calls are limited to one per second.)
+ *
+ * @return int Number of indeterminate progress calls
+ */
+ public function get_progress_count() {
+ return $this->count;
+ }
+}
--- /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/>.
+
+/**
+ * Unit tests for the progress classes.
+ *
+ * @package core_backup
+ * @category phpunit
+ * @copyright 2013 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+// Include all the needed stuff.
+global $CFG;
+require_once($CFG->dirroot . '/backup/util/progress/core_backup_progress.class.php');
+
+/**
+ * Progress tests.
+ */
+class backup_progress_testcase extends basic_testcase {
+
+ /**
+ * Tests for basic use with simple numeric progress.
+ */
+ public function test_basic() {
+ $progress = new core_backup_mock_progress();
+
+ // Check values of empty progress things.
+ $this->assertFalse($progress->is_in_progress_section());
+
+ // Start progress counting, check basic values and check that update
+ // gets called.
+ $progress->start_progress('hello', 10);
+ $this->assertTrue($progress->was_update_called());
+ $this->assertTrue($progress->is_in_progress_section());
+ $this->assertEquals('hello', $progress->get_current_description());
+
+ // Check numeric position and indeterminate count.
+ $this->assert_min_max(0.0, 0.0, $progress);
+ $this->assertEquals(0, $progress->get_progress_count());
+
+ // Make some progress and check that the time limit gets added.
+ $progress->step_time();
+ $progress->progress(2);
+ $this->assertTrue($progress->was_update_called());
+ $this->assertEquals(120, ini_get('max_execution_time'));
+
+ // Check the new value.
+ $this->assert_min_max(0.2, 0.2, $progress);
+
+ // Do another progress run at same time, it should be ignored.
+ $progress->progress(3);
+ $this->assertFalse($progress->was_update_called());
+ $this->assert_min_max(0.2, 0.2, $progress);
+
+ // End the section. This should cause an update.
+ $progress->end_progress();
+ $this->assertTrue($progress->was_update_called());
+
+ // Because there are no sections left open, it thinks we finished.
+ $this->assert_min_max(1.0, 1.0, $progress);
+
+ // There was 1 progress call.
+ $this->assertEquals(1, $progress->get_progress_count());
+
+ // Clear the time limit, otherwise phpunit complains.
+ set_time_limit(0);
+ }
+
+ /**
+ * Tests progress that is nested and/or indeterminate.
+ */
+ public function test_nested() {
+ // Outer progress goes from 0 to 10.
+ $progress = new core_backup_mock_progress();
+ $progress->start_progress('hello', 10);
+
+ // Get up to 4, check position.
+ $progress->step_time();
+ $progress->progress(4);
+ $this->assert_min_max(0.4, 0.4, $progress);
+ $this->assertEquals('hello', $progress->get_current_description());
+
+ // Now start indeterminate progress.
+ $progress->start_progress('world');
+ $this->assert_min_max(0.4, 0.5, $progress);
+ $this->assertEquals('world', $progress->get_current_description());
+
+ // Do some indeterminate progress and count it (once per second).
+ $progress->step_time();
+ $progress->progress();
+ $this->assertEquals(2, $progress->get_progress_count());
+ $progress->progress();
+ $this->assertEquals(2, $progress->get_progress_count());
+ $progress->step_time();
+ $progress->progress();
+ $this->assertEquals(3, $progress->get_progress_count());
+ $this->assert_min_max(0.4, 0.5, $progress);
+
+ // Exit the indeterminate section.
+ $progress->end_progress();
+ $this->assert_min_max(0.5, 0.5, $progress);
+
+ $progress->step_time();
+ $progress->progress(7);
+ $this->assert_min_max(0.7, 0.7, $progress);
+
+ // Enter a numbered section (this time with a range of 5).
+ $progress->start_progress('frogs', 5);
+ $this->assert_min_max(0.7, 0.7, $progress);
+ $progress->step_time();
+ $progress->progress(1);
+ $this->assert_min_max(0.72, 0.72, $progress);
+ $progress->step_time();
+ $progress->progress(3);
+ $this->assert_min_max(0.76, 0.76, $progress);
+
+ // Now enter another indeterminate section.
+ $progress->start_progress('and');
+ $this->assert_min_max(0.76, 0.78, $progress);
+
+ // Make some progress, should increment indeterminate count.
+ $progress->step_time();
+ $progress->progress();
+ $this->assertEquals(7, $progress->get_progress_count());
+
+ // Enter numbered section, won't make any difference to values.
+ $progress->start_progress('zombies', 2);
+ $progress->step_time();
+ $progress->progress(1);
+ $this->assert_min_max(0.76, 0.78, $progress);
+ $this->assertEquals(8, $progress->get_progress_count());
+
+ // Leaving it will make no difference too.
+ $progress->end_progress();
+
+ // Leaving the indeterminate section will though.
+ $progress->end_progress();
+ $this->assert_min_max(0.78, 0.78, $progress);
+
+ // Leave the two numbered sections.
+ $progress->end_progress();
+ $this->assert_min_max(0.8, 0.8, $progress);
+ $progress->end_progress();
+ $this->assertFalse($progress->is_in_progress_section());
+
+ set_time_limit(0);
+ }
+
+ /**
+ * Tests the feature for 'weighting' nested progress.
+ */
+ public function test_nested_weighted() {
+ $progress = new core_backup_mock_progress();
+ $progress->start_progress('', 10);
+
+ // First nested child has 2 units of its own and is worth 1 unit.
+ $progress->start_progress('', 2);
+ $progress->step_time();
+ $progress->progress(1);
+ $this->assert_min_max(0.05, 0.05, $progress);
+ $progress->end_progress();
+ $this->assert_min_max(0.1, 0.1, $progress);
+
+ // Next child has 2 units of its own but is worth 3 units.
+ $progress->start_progress('weighted', 2, 3);
+ $progress->step_time();
+ $progress->progress(1);
+ $this->assert_min_max(0.25, 0.25, $progress);
+ $progress->end_progress();
+ $this->assert_min_max(0.4, 0.4, $progress);
+
+ // Next indeterminate child is worth 6 units.
+ $progress->start_progress('', core_backup_progress::INDETERMINATE, 6);
+ $progress->step_time();
+ $progress->progress();
+ $this->assert_min_max(0.4, 1.0, $progress);
+ $progress->end_progress();
+ $this->assert_min_max(1.0, 1.0, $progress);
+
+ set_time_limit(0);
+ }
+
+ /**
+ * I had some issues with real use in backup/restore, this test is intended
+ * to be similar.
+ */
+ public function test_realistic() {
+ $progress = new core_backup_mock_progress();
+ $progress->start_progress('parent', 100);
+ $progress->start_progress('child', 1);
+ $progress->progress(1);
+ $this->assert_min_max(0.01, 0.01, $progress);
+ $progress->end_progress();
+ $this->assert_min_max(0.01, 0.01, $progress);
+
+ // Clear the time limit, otherwise phpunit complains.
+ set_time_limit(0);
+ }
+
+ /**
+ * Tests for any exceptions due to invalid calls.
+ */
+ public function test_exceptions() {
+ $progress = new core_backup_mock_progress();
+
+ // Check errors when empty.
+ try {
+ $progress->progress();
+ $this->fail();
+ } catch (coding_exception $e) {
+ $this->assertEquals(1, preg_match('~without start_progress~', $e->getMessage()));
+ }
+ try {
+ $progress->end_progress();
+ $this->fail();
+ } catch (coding_exception $e) {
+ $this->assertEquals(1, preg_match('~without start_progress~', $e->getMessage()));
+ }
+ try {
+ $progress->get_current_description();
+ $this->fail();
+ } catch (coding_exception $e) {
+ $this->assertEquals(1, preg_match('~Not inside progress~', $e->getMessage()));
+ }
+ try {
+ $progress->start_progress('', 1, 7);
+ $this->fail();
+ } catch (coding_exception $e) {
+ $this->assertEquals(1, preg_match('~must be 1~', $e->getMessage()));
+ }
+
+ // Check invalid start (0).
+ try {
+ $progress->start_progress('hello', 0);
+ $this->fail();
+ } catch (coding_exception $e) {
+ $this->assertEquals(1, preg_match('~cannot be zero or negative~', $e->getMessage()));
+ }
+
+ // Indeterminate when value expected.
+ $progress->start_progress('hello', 10);
+ try {
+ $progress->progress(core_backup_progress::INDETERMINATE);
+ $this->fail();
+ } catch (coding_exception $e) {
+ $this->assertEquals(1, preg_match('~expecting value~', $e->getMessage()));
+ }
+
+ // Value when indeterminate expected.
+ $progress->start_progress('hello');
+ try {
+ $progress->progress(4);
+ $this->fail();
+ } catch (coding_exception $e) {
+ $this->assertEquals(1, preg_match('~expecting INDETERMINATE~', $e->getMessage()));
+ }
+
+ // Illegal values.
+ $progress->start_progress('hello', 10);
+ try {
+ $progress->progress(-2);
+ $this->fail();
+ } catch (coding_exception $e) {
+ $this->assertEquals(1, preg_match('~out of range~', $e->getMessage()));
+ }
+ try {
+ $progress->progress(11);
+ $this->fail();
+ } catch (coding_exception $e) {
+ $this->assertEquals(1, preg_match('~out of range~', $e->getMessage()));
+ }
+
+ // You are allowed two with the same value...
+ $progress->progress(4);
+ $progress->step_time();
+ $progress->progress(4);
+ $progress->step_time();
+
+ // ...but not to go backwards.
+ try {
+ $progress->progress(3);
+ $this->fail();
+ } catch (coding_exception $e) {
+ $this->assertEquals(1, preg_match('~backwards~', $e->getMessage()));
+ }
+
+ // When you go forward, you can't go further than there is room.
+ try {
+ $progress->start_progress('', 1, 7);
+ $this->fail();
+ } catch (coding_exception $e) {
+ $this->assertEquals(1, preg_match('~would exceed max~', $e->getMessage()));
+ }
+
+ // Clear the time limit, otherwise phpunit complains.
+ set_time_limit(0);
+ }
+
+ /**
+ * Checks the current progress values are as expected.
+ *
+ * @param number $min Expected min progress
+ * @param number $max Expected max progress
+ * @param core_backup_mock_progress $progress
+ */
+ private function assert_min_max($min, $max, core_backup_mock_progress $progress) {
+ $this->assertEquals(array($min, $max),
+ $progress->get_progress_proportion_range());
+ }
+}
+
+/**
+ * Helper class that records when update_progress is called and allows time
+ * stepping.
+ */
+class core_backup_mock_progress extends core_backup_progress {
+ private $updatecalled = false;
+ private $time = 1;
+
+ /**
+ * Checks if update was called since the last call to this function.
+ *
+ * @return boolean True if update was called
+ */
+ public function was_update_called() {
+ if ($this->updatecalled) {
+ $this->updatecalled = false;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Steps the current time by 1 second.
+ */
+ public function step_time() {
+ $this->time++;
+ }
+
+ protected function update_progress() {
+ $this->updatecalled = true;
+ }
+
+ protected function get_time() {
+ return $this->time;
+ }
+}
$this->totalcount = 0;
$contextlevel = $this->get_itemcontextlevel();
list($sql, $params) = $this->get_searchsql();
- $blocksz = 5000;
- $offs = 0;
- // Get total number, to avoid some incorrect iterations
+ // Get total number, to avoid some incorrect iterations.
$countsql = preg_replace('/ORDER BY.*/', '', $sql);
$totalcourses = $DB->count_records_sql("SELECT COUNT(*) FROM ($countsql) sel", $params);
- // User to be checked is always the same (usually null, get it form first element)
- $firstcap = reset($this->requiredcapabilities);
- $userid = isset($firstcap['user']) ? $firstcap['user'] : null;
- // Extract caps to check, this saves us a bunch of iterations
- $requiredcaps = array();
- foreach ($this->requiredcapabilities as $cap) {
- $requiredcaps[] = $cap['capability'];
- }
- // Iterate while we have records and haven't reached $this->maxresults.
- while ($totalcourses > $offs and $this->totalcount < $this->maxresults) {
- $resultset = $DB->get_records_sql($sql, $params, $offs, $blocksz);
+ if ($totalcourses > 0) {
+ // User to be checked is always the same (usually null, get it from first element).
+ $firstcap = reset($this->requiredcapabilities);
+ $userid = isset($firstcap['user']) ? $firstcap['user'] : null;
+ // Extract caps to check, this saves us a bunch of iterations.
+ $requiredcaps = array();
+ foreach ($this->requiredcapabilities as $cap) {
+ $requiredcaps[] = $cap['capability'];
+ }
+ // Iterate while we have records and haven't reached $this->maxresults.
+ $resultset = $DB->get_recordset_sql($sql, $params);
foreach ($resultset as $result) {
context_helper::preload_from_record($result);
$classname = context_helper::get_class_for_level($contextlevel);
$this->totalcount++;
$this->results[$result->id] = $result;
}
- $offs += $blocksz;
+ $resultset->close();
}
return $this->totalcount;
/**
* Triggered when 'course_completed' event is triggered.
*
- * @param \core\event\course_completed $event
+ * @param \core\event\course_completed $event
*/
public static function course_criteria_review(\core\event\course_completed $event) {
global $DB, $CFG;
}
}
}
+
+ /**
+ * Triggered when 'user_updated' event happens.
+ *
+ * @param \core\event\user_updated $event event generated when user profile is updated.
+ */
+ public static function profile_criteria_review(\core\event\user_updated $event) {
+ global $DB, $CFG;
+
+ if (!empty($CFG->enablebadges)) {
+ require_once($CFG->dirroot.'/lib/badgeslib.php');
+ $userid = $event->objectid;
+
+ if ($rs = $DB->get_records('badge_criteria', array('criteriatype' => BADGE_CRITERIA_TYPE_PROFILE))) {
+ foreach ($rs as $r) {
+ $badge = new badge($r->badgeid);
+ if (!$badge->is_active() || $badge->is_issued($userid)) {
+ continue;
+ }
+
+ if ($badge->criteria[BADGE_CRITERIA_TYPE_PROFILE]->review($userid)) {
+ $badge->criteria[BADGE_CRITERIA_TYPE_PROFILE]->mark_complete($userid);
+
+ if ($badge->criteria[BADGE_CRITERIA_TYPE_OVERALL]->review($userid)) {
+ $badge->criteria[BADGE_CRITERIA_TYPE_OVERALL]->mark_complete($userid);
+ $badge->issue($userid);
+ }
+ }
+ }
+ }
+ }
+ }
}
require_once(dirname(dirname(__FILE__)) . '/config.php');
require_once($CFG->libdir . '/badgeslib.php');
-$json = required_param('badge', PARAM_RAW);
+$json = optional_param('badge', null, PARAM_RAW);
+// Redirect to homepage if users are trying to access external badge through old url.
+if ($json) {
+ redirect($CFG->wwwroot, get_string('invalidrequest', 'error'), 3);
+}
+
+$hash = required_param('hash', PARAM_ALPHANUM);
+$userid = required_param('user', PARAM_INT);
+
+$PAGE->set_url(new moodle_url('/badges/external.php', array('hash' => $hash, 'user' => $userid)));
+
+// Using the same setting as user profile page.
+if (!empty($CFG->forceloginforprofiles)) {
+ require_login();
+ if (isguestuser()) {
+ $SESSION->wantsurl = $PAGE->url->out(false);
+ redirect(get_login_url());
+ }
+} else if (!empty($CFG->forcelogin)) {
+ require_login();
+}
+
+// Get all external badges of a user.
+$out = get_backpack_settings($userid);
+$badges = $out->badges;
+
+// Loop through the badges and check if supplied badge hash exists in user external badges.
+foreach ($badges as $b) {
+ if ($hash == hash("md5", $b->hostedUrl)) {
+ $badge = $b;
+ break;
+ }
+}
+
+// If we didn't find the badge, a user might be trying to replace userid parameter.
+if (is_null($badge)) {
+ print_error(get_string('error:externalbadgedoesntexist', 'badges'));
+}
$PAGE->set_context(context_system::instance());
$output = $PAGE->get_renderer('core', 'badges');
-$badge = new external_badge(unserialize($json));
+$badge = new external_badge($badge, $userid);
-$PAGE->set_url('/badges/external.php');
$PAGE->set_pagelayout('base');
$PAGE->set_title(get_string('issuedbadge', 'badges'));
$PAGE->set_pagelayout('mydashboard');
$backpack = $DB->get_record('badge_backpack', array('userid' => $USER->id));
+$badgescache = cache::make('core', 'externalbadges');
if ($disconnect && $backpack) {
require_sesskey();
$DB->delete_records('badge_external', array('backpackid' => $backpack->id));
$DB->delete_records('badge_backpack', array('userid' => $USER->id));
+ $badgescache->delete($USER->id);
redirect(new moodle_url('/badges/mybackpack.php'));
}
$DB->insert_record('badge_external', $obj);
}
}
+ $badgescache->delete($USER->id);
redirect(new moodle_url('/badges/mybadges.php'));
}
} else {
$url = new moodle_url('badge.php', array('hash' => $badge->uniquehash));
} else {
if (!$external) {
- $url = new moodle_url($CFG->wwwroot . '/badges/badge.php', array('hash' => $badge->uniquehash));
+ $url = new moodle_url('/badges/badge.php', array('hash' => $badge->uniquehash));
} else {
- $url = new moodle_url($CFG->wwwroot . '/badges/external.php', array('badge' => serialize($badge)));
+ $hash = hash('md5', $badge->hostedUrl);
+ $url = new moodle_url('/badges/external.php', array('hash' => $hash, 'user' => $userid));
}
}
$actions = html_writer::tag('div', $push . $download . $status, array('class' => 'badge-actions'));
protected function render_issued_badge(issued_badge $ibadge) {
global $USER, $CFG, $DB;
$issued = $ibadge->issued;
+ $userinfo = $ibadge->recipient;
$badge = new badge($ibadge->badgeid);
$today_date = date('Y-m-d');
$today = strtotime($today_date);
$imagetable = new html_table();
$imagetable->attributes = array('class' => 'clearfix badgeissuedimage');
$imagetable->data[] = array(html_writer::empty_tag('img', array('src' => $issued['badge']['image'])));
- if ($USER->id == $ibadge->recipient && !empty($CFG->enablebadges)) {
+ if ($USER->id == $userinfo->id && !empty($CFG->enablebadges)) {
$imagetable->data[] = array($this->output->single_button(
new moodle_url('/badges/badge.php', array('hash' => $ibadge->hash, 'bake' => true)),
get_string('download'),
$datatable = new html_table();
$datatable->attributes = array('class' => 'badgeissuedinfo');
$datatable->colclasses = array('bfield', 'bvalue');
+
+ // Recipient information.
+ $datatable->data[] = array($this->output->heading(get_string('recipientdetails', 'badges'), 3), '');
+ $datatable->data[] = array(get_string('name'), fullname($userinfo));
+ if (empty($userinfo->backpackemail)) {
+ $datatable->data[] = array(get_string('email'), obfuscate_mailto($userinfo->accountemail));
+ } else {
+ $datatable->data[] = array(get_string('email'), obfuscate_mailto($userinfo->backpackemail));
+ }
+
$datatable->data[] = array($this->output->heading(get_string('issuerdetails', 'badges'), 3), '');
$datatable->data[] = array(get_string('issuername', 'badges'), $badge->issuername);
if (isset($badge->issuercontact) && !empty($badge->issuercontact)) {
- $datatable->data[] = array(get_string('contact', 'badges'),
- html_writer::tag('a', $badge->issuercontact, array('href' => 'mailto:' . $badge->issuercontact)));
+ $datatable->data[] = array(get_string('contact', 'badges'), obfuscate_mailto($badge->issuercontact));
}
$datatable->data[] = array($this->output->heading(get_string('badgedetails', 'badges'), 3), '');
$datatable->data[] = array(get_string('name'), $badge->name);
// Print evidence.
$agg = $badge->get_aggregation_methods();
- $evidence = $badge->get_criteria_completions($ibadge->recipient);
+ $evidence = $badge->get_criteria_completions($userinfo->id);
$eids = array_map(create_function('$o', 'return $o->critid;'), $evidence);
unset($badge->criteria[BADGE_CRITERIA_TYPE_OVERALL]);
$issued = $ibadge->issued;
$assertion = $issued->assertion;
$issuer = $assertion->badge->issuer;
+ $userinfo = $ibadge->recipient;
$table = new html_table();
$imagetable = new html_table();
$datatable = new html_table();
$datatable->attributes = array('class' => 'badgeissuedinfo');
$datatable->colclasses = array('bfield', 'bvalue');
+
+ // Recipient information.
+ $datatable->data[] = array($this->output->heading(get_string('recipientdetails', 'badges'), 3), '');
+ // Technically, we should alway have a user at this point, but added an extra check just in case.
+ if ($userinfo) {
+ $datatable->data[] = array(get_string('name'), fullname($userinfo));
+ if (!$ibadge->valid) {
+ $notify = $this->output->notification(get_string('recipientvalidationproblem', 'badges'), 'notifynotice');
+ $datatable->data[] = array(get_string('email'), obfuscate_mailto($userinfo->email) . $notify);
+ } else {
+ $datatable->data[] = array(get_string('email'), obfuscate_mailto($userinfo->email));
+ }
+ } else {
+ $notify = $this->output->notification(get_string('recipientidentificationproblem', 'badges'), 'notifynotice');
+ $datatable->data[] = array(get_string('name'), $notify);
+ }
+
$datatable->data[] = array($this->output->heading(get_string('issuerdetails', 'badges'), 3), '');
$datatable->data[] = array(get_string('issuername', 'badges'), $issuer->name);
$datatable->data[] = array(get_string('issuerurl', 'badges'),
html_writer::tag('a', $issuer->origin, array('href' => $issuer->origin)));
if (isset($issuer->contact)) {
- $datatable->data[] = array(get_string('contact', 'badges'),
- html_writer::tag('a', $issuer->contact, array('href' => 'mailto:' . $issuer->contact)));
+ $datatable->data[] = array(get_string('contact', 'badges'), obfuscate_mailto($issuer->contact));
}
$datatable->data[] = array($this->output->heading(get_string('badgedetails', 'badges'), 3), '');
$datatable->data[] = array(get_string('name'), $assertion->badge->name);
public $issued;
/** @var badge recipient */
- public $recipient = 0;
+ public $recipient;
/** @var badge visibility to others */
public $visible = 0;
WHERE ' . $DB->sql_compare_text('uniquehash', 40) . ' = ' . $DB->sql_compare_text(':hash', 40),
array('hash' => $hash), IGNORE_MISSING);
if ($rec) {
- $this->recipient = $rec->userid;
+ // Get a recipient from database.
+ $user = $DB->get_record_sql('SELECT u.id, u.lastname, u.firstname,
+ u.email AS accountemail, b.email AS backpackemail
+ FROM {user} u LEFT JOIN {badge_backpack} b ON u.id = b.userid
+ WHERE u.id = :userid', array('userid' => $rec->userid));
+ $this->recipient = $user;
$this->visible = $rec->visible;
$this->badgeid = $rec->badgeid;
}
/** @var issued badge */
public $issued;
+ /** @var User ID */
+ public $recipient;
+
+ /** @var validation of external badge */
+ public $valid = true;
+
/**
* Initializes the badge to display
*
- * @param string $json External badge information.
+ * @param object $badge External badge information.
+ * @param int $recipient User id.
*/
- public function __construct($json) {
- $this->issued = $json;
+ public function __construct($badge, $recipient) {
+ global $DB;
+ // At this point a user has connected a backpack. So, we are going to get
+ // their backpack email rather than their account email.
+ $user = $DB->get_record_sql('SELECT u.lastname, u.firstname, b.email
+ FROM {user} u INNER JOIN {badge_backpack} b ON u.id = b.userid
+ WHERE userid = :userid', array('userid' => $recipient), IGNORE_MISSING);
+
+ $this->issued = $badge;
+ $this->recipient = $user;
+
+ // Check if recipient is valid.
+ // There is no way to be 100% sure that a badge belongs to a user.
+ // Backpack does not return any recipient information.
+ // All we can do is compare that backpack email hashed using salt
+ // provided in the assertion matches a badge recipient from the assertion.
+ if ($user) {
+ if (validate_email($badge->assertion->recipient) && $badge->assertion->recipient == $user->email) {
+ // If we have email, compare emails.
+ $this->valid = true;
+ } else if ($badge->assertion->recipient == 'sha256$' . hash('sha256', $user->email)) {
+ // If recipient is hashed, but no salt, compare hashes without salt.
+ $this->valid = true;
+ } else if ($badge->assertion->recipient == 'sha256$' . hash('sha256', $user->email . $badge->assertion->salt)) {
+ // If recipient is hashed, compare hashes.
+ $this->valid = true;
+ } else {
+ // Otherwise, we cannot be sure that this user is a recipient.
+ $this->valid = false;
+ }
+ } else {
+ $this->valid = false;
+ }
}
}
parent::__construct($badges);
if (!empty($CFG->badges_allowexternalbackpack)) {
- $this->backpack = get_backpack_settings($userid);
+ $this->backpack = get_backpack_settings($userid, true);
}
}
}
$this->assertDebuggingCalled('Error baking badge image!');
$this->assertTrue($badge->is_issued($this->user->id));
}
+
+ /**
+ * Test badges observer when user_updated event is fired.
+ */
+ public function test_badges_observer_profile_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_PROFILE, 'badgeid' => $badge->id));
+ $criteria_overall1->save(array('agg' => BADGE_CRITERIA_AGGREGATION_ALL, 'field_address' => 'address'));
+
+ $this->user->address = 'Test address';
+ user_update_user($this->user, false);
+ // Check if badge is awarded.
+ $this->assertDebuggingCalled('Error baking badge image!');
+ $this->assertTrue($badge->is_issued($this->user->id));
+ }
}
if (array_key_exists('shortname', $course) && ($oldcourse->shortname != $course['shortname'])) {
require_capability('moodle/course:changeshortname', $context);
if ($DB->record_exists('course', array('shortname' => $course['shortname']))) {
- throw new moodle_exception('shortnametaken');
+ throw new moodle_exception('shortnametaken', '', '', $course['shortname']);
}
}
if (array_key_exists('idnumber', $course) && ($oldcourse->idnumber != $course['idnumber'])) {
require_capability('moodle/course:changeidnumber', $context);
if ($DB->record_exists('course', array('idnumber' => $course['idnumber']))) {
- throw new moodle_exception('idnumbertaken');
+ throw new moodle_exception('courseidnumbertaken', '', '', $course['idnumber']);
}
}
//check the categoryid - must be given for all new courses
$category = $DB->get_record('course_categories', array('id'=>$data->category), '*', MUST_EXIST);
- //check if the shortname already exist
+ // Check if the shortname already exists.
if (!empty($data->shortname)) {
if ($DB->record_exists('course', array('shortname' => $data->shortname))) {
- throw new moodle_exception('shortnametaken');
+ throw new moodle_exception('shortnametaken', '', '', $data->shortname);
}
}
- //check if the id number already exist
+ // Check if the idnumber already exists.
if (!empty($data->idnumber)) {
if ($DB->record_exists('course', array('idnumber' => $data->idnumber))) {
- throw new moodle_exception('idnumbertaken');
+ throw new moodle_exception('courseidnumbertaken', '', '', $data->idnumber);
}
}
// Ensure blocks have been associated to the course.
$blockcount = $DB->count_records('block_instances', array('parentcontextid' => $context->id));
$this->assertGreaterThan(0, $blockcount);
+
+ // Ensure that the shortname isn't duplicated.
+ try {
+ $created = create_course($course);
+ $this->fail('Exception expected');
+ } catch (moodle_exception $e) {
+ $this->assertSame(get_string('shortnametaken', 'error', $course->shortname), $e->getMessage());
+ }
+
+ // Ensure that the idnumber isn't duplicated.
+ $course->shortname .= '1';
+ try {
+ $created = create_course($course);
+ $this->fail('Exception expected');
+ } catch (moodle_exception $e) {
+ $this->assertSame(get_string('courseidnumbertaken', 'error', $course->idnumber), $e->getMessage());
+ }
}
public function test_create_course_with_generator() {
$string['createcourseextid'] = 'CREATE User enrolled to a nonexistant course \'{$a->courseextid}\'';
$string['createnotcourseextid'] = 'User enrolled to a nonexistant course \'{$a->courseextid}\'';
$string['creatingcourse'] = 'Creating course \'{$a}\'...';
+$string['duplicateshortname'] = "Course creation failed. Duplicate short name. Skipping course with idnumber '{\$a->idnumber}'...";
$string['editlock'] = 'Lock value';
$string['emptyenrolment'] = "Empty enrolment for role '{\$a->role_shortname}' in course '{\$a->course_shortname}'\n";
$string['enrolname'] = 'LDAP';
$course->summary = $course_ext[$this->get_config('course_summary')][0];
}
+ // Check if the shortname already exists if it does - skip course creation.
+ if ($DB->record_exists('course', array('shortname' => $course->shortname))) {
+ $trace->output(get_string('duplicateshortname', 'enrol_ldap', $course));
+ return false;
+ }
+
$newcourse = create_course($course);
return $newcourse->id;
}
foreach ($user->enrolments as $enrolment) {
$enrolment->courseid = $enrolment->enrolmentinstance->courseid;
$enrolment->enrol = 'manual';
- events_trigger('user_enrol_modified', $enrolment);
+ // Trigger event.
+ $event = \core\event\user_enrolment_updated::create(
+ array(
+ 'objectid' => $enrolment->id,
+ 'courseid' => $enrolment->courseid,
+ 'context' => context_course::instance($enrolment->courseid),
+ 'relateduserid' => $user->id,
+ 'other' => array('enrol' => 'manual')
+ )
+ );
+ $event->trigger();
}
}
return true;
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Event observer for meta enrolment plugin.
+ *
+ * @package enrol_meta
+ * @copyright 2013 Rajesh Taneja <rajesh@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->dirroot.'/enrol/meta/locallib.php');
+
+/**
+ * Event observer for enrol_meta.
+ *
+ * @package enrol_meta
+ * @copyright 2013 Rajesh Taneja <rajesh@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class enrol_meta_observer extends enrol_meta_handler {
+
+ /**
+ * Triggered via user_enrolment_created event.
+ *
+ * @param \core\event\user_enrolment_created $event
+ * @return bool true on success.
+ */
+ public static function user_enrolment_created(\core\event\user_enrolment_created $event) {
+ if (!enrol_is_enabled('meta')) {
+ // No more enrolments for disabled plugins.
+ return true;
+ }
+
+ if ($event->other['enrol'] === 'meta') {
+ // Prevent circular dependencies - we can not sync meta enrolments recursively.
+ return true;
+ }
+
+ self::sync_course_instances($event->courseid, $event->relateduserid);
+ return true;
+ }
+
+ /**
+ * Triggered via user_enrolment_deleted event.
+ *
+ * @param \core\event\user_enrolment_deleted $event
+ * @return bool true on success.
+ */
+ public static function user_enrolment_deleted(\core\event\user_enrolment_deleted $event) {
+ if (!enrol_is_enabled('meta')) {
+ // This is slow, let enrol_meta_sync() deal with disabled plugin.
+ return true;
+ }
+
+ if ($event->other['enrol'] === 'meta') {
+ // Prevent circular dependencies - we can not sync meta enrolments recursively.
+ return true;
+ }
+
+ self::sync_course_instances($event->courseid, $event->relateduserid);
+
+ return true;
+ }
+
+ /**
+ * Triggered via user_enrolment_updated event.
+ *
+ * @param \core\event\user_enrolment_updated $event
+ * @return bool true on success
+ */
+ public static function user_enrolment_updated(\core\event\user_enrolment_updated $event) {
+ if (!enrol_is_enabled('meta')) {
+ // No modifications if plugin disabled.
+ return true;
+ }
+
+ if ($event->other['enrol'] === 'meta') {
+ // Prevent circular dependencies - we can not sync meta enrolments recursively.
+ return true;
+ }
+
+ self::sync_course_instances($event->courseid, $event->relateduserid);
+
+ return true;
+ }
+
+ /**
+ * Triggered via role_assigned event.
+ *
+ * @param \core\event\role_assigned $event
+ * @return bool true on success.
+ */
+ public static function role_assigned(\core\event\role_assigned $event) {
+ if (!enrol_is_enabled('meta')) {
+ return true;
+ }
+
+ // Prevent circular dependencies - we can not sync meta roles recursively.
+ if ($event->other['component'] === 'enrol_meta') {
+ return true;
+ }
+
+ // Only course level roles are interesting.
+ if (!$parentcontext = context::instance_by_id($event->contextid, IGNORE_MISSING)) {
+ return true;
+ }
+ if ($parentcontext->contextlevel != CONTEXT_COURSE) {
+ return true;
+ }
+
+ self::sync_course_instances($parentcontext->instanceid, $event->relateduserid);
+
+ return true;
+ }
+
+ /**
+ * Triggered via role_unassigned event.
+ *
+ * @param \core\event\role_unassigned $event
+ * @return bool true on success
+ */
+ public static function role_unassigned(\core\event\role_unassigned $event) {
+ if (!enrol_is_enabled('meta')) {
+ // All roles are removed via cron automatically.
+ return true;
+ }
+
+ // Prevent circular dependencies - we can not sync meta roles recursively.
+ if ($event->other['component'] === 'enrol_meta') {
+ return true;
+ }
+
+ // Only course level roles are interesting.
+ if (!$parentcontext = context::instance_by_id($event->contextid, IGNORE_MISSING)) {
+ return true;
+ }
+ if ($parentcontext->contextlevel != CONTEXT_COURSE) {
+ return true;
+ }
+
+ self::sync_course_instances($parentcontext->instanceid, $event->relateduserid);
+
+ return true;
+ }
+
+ /**
+ * Triggered via course_deleted event.
+ *
+ * @param \core\event\course_deleted $event
+ * @return bool true on success
+ */
+ public static function course_deleted(\core\event\course_deleted $event) {
+ global $DB;
+
+ if (!enrol_is_enabled('meta')) {
+ // This is slow, let enrol_meta_sync() deal with disabled plugin.
+ return true;
+ }
+
+ // Does anything want to sync with this parent?
+ if (!$enrols = $DB->get_records('enrol', array('customint1' => $event->objectid, 'enrol' => 'meta'),
+ 'courseid ASC, id ASC')) {
+ return true;
+ }
+
+ $plugin = enrol_get_plugin('meta');
+ $unenrolaction = $plugin->get_config('unenrolaction', ENROL_EXT_REMOVED_SUSPENDNOROLES);
+
+ if ($unenrolaction == ENROL_EXT_REMOVED_UNENROL) {
+ // Simple, just delete this instance which purges all enrolments,
+ // admins were warned that this is risky setting!
+ foreach ($enrols as $enrol) {
+ $plugin->delete_instance($enrol);
+ }
+ return true;
+ }
+
+ foreach ($enrols as $enrol) {
+ $enrol->customint = 0;
+ $DB->update_record('enrol', $enrol);
+
+ if ($unenrolaction == ENROL_EXT_REMOVED_SUSPEND or $unenrolaction == ENROL_EXT_REMOVED_SUSPENDNOROLES) {
+ // This makes all enrolments suspended very quickly.
+ $plugin->update_status($enrol, ENROL_INSTANCE_DISABLED);
+ }
+ if ($unenrolaction == ENROL_EXT_REMOVED_SUSPENDNOROLES) {
+ $context = context_course::instance($enrol->courseid);
+ role_unassign_all(array('contextid'=>$context->id, 'component'=>'enrol_meta', 'itemid'=>$enrol->id));
+ }
+ }
+
+ return true;
+ }
+}
defined('MOODLE_INTERNAL') || die();
-/* List of handlers */
-$handlers = array (
- 'role_assigned' => array (
- 'handlerfile' => '/enrol/meta/locallib.php',
- 'handlerfunction' => array('enrol_meta_handler', 'role_assigned'),
- 'schedule' => 'instant',
- 'internal' => 1,
- ),
+// List of observers.
+$observers = array(
- 'role_unassigned' => array (
- 'handlerfile' => '/enrol/meta/locallib.php',
- 'handlerfunction' => array('enrol_meta_handler', 'role_unassigned'),
- 'schedule' => 'instant',
- 'internal' => 1,
+ array(
+ 'eventname' => '\core\event\user_enrolment_created',
+ 'callback' => 'enrol_meta_observer::user_enrolment_created',
),
-
- 'user_enrolled' => array (
- 'handlerfile' => '/enrol/meta/locallib.php',
- 'handlerfunction' => array('enrol_meta_handler', 'user_enrolled'),
- 'schedule' => 'instant',
- 'internal' => 1,
+ array(
+ 'eventname' => '\core\event\user_enrolment_deleted',
+ 'callback' => 'enrol_meta_observer::user_enrolment_deleted',
),
-
- 'user_unenrolled' => array (
- 'handlerfile' => '/enrol/meta/locallib.php',
- 'handlerfunction' => array('enrol_meta_handler', 'user_unenrolled'),
- 'schedule' => 'instant',
- 'internal' => 1,
+ array(
+ 'eventname' => '\core\event\user_enrolment_updated',
+ 'callback' => 'enrol_meta_observer::user_enrolment_updated',
),
-
- 'user_enrol_modified' => array (
- 'handlerfile' => '/enrol/meta/locallib.php',
- 'handlerfunction' => array('enrol_meta_handler', 'user_enrol_modified'),
- 'schedule' => 'instant',
- 'internal' => 1,
+ array(
+ 'eventname' => '\core\event\role_assigned',
+ 'callback' => 'enrol_meta_observer::role_assigned',
),
-
- 'course_deleted' => array (
- 'handlerfile' => '/enrol/meta/locallib.php',
- 'handlerfunction' => array('enrol_meta_handler', 'course_deleted'),
- 'schedule' => 'instant',
- 'internal' => 1,
+ array(
+ 'eventname' => '\core\event\role_unassigned',
+ 'callback' => 'enrol_meta_observer::role_unassigned',
+ ),
+ array(
+ 'eventname' => '\core\event\course_deleted',
+ 'callback' => 'enrol_meta_observer::course_deleted',
),
);
debugging('Unknown unenrol action '.$unenrolaction);
}
}
-
- /**
- * Triggered via role assigned event.
- * @static
- * @param stdClass $ra
- * @return bool success
- */
- public static function role_assigned($ra) {
- if (!enrol_is_enabled('meta')) {
- return true;
- }
-
- // prevent circular dependencies - we can not sync meta roles recursively
- if ($ra->component === 'enrol_meta') {
- return true;
- }
-
- // only course level roles are interesting
- if (!$parentcontext = context::instance_by_id($ra->contextid, IGNORE_MISSING)) {
- return true;
- }
- if ($parentcontext->contextlevel != CONTEXT_COURSE) {
- return true;
- }
-
- self::sync_course_instances($parentcontext->instanceid, $ra->userid);
-
- return true;
- }
-
- /**
- * Triggered via role unassigned event.
- * @static
- * @param stdClass $ra
- * @return bool success
- */
- public static function role_unassigned($ra) {
- if (!enrol_is_enabled('meta')) {
- // all roles are removed via cron automatically
- return true;
- }
-
- // prevent circular dependencies - we can not sync meta roles recursively
- if ($ra->component === 'enrol_meta') {
- return true;
- }
-
- // only course level roles are interesting
- if (!$parentcontext = context::instance_by_id($ra->contextid, IGNORE_MISSING)) {
- return true;
- }
- if ($parentcontext->contextlevel != CONTEXT_COURSE) {
- return true;
- }
-
- self::sync_course_instances($parentcontext->instanceid, $ra->userid);
-
- return true;
- }
-
- /**
- * Triggered via user enrolled event.
- * @static
- * @param stdClass $ue
- * @return bool success
- */
- public static function user_enrolled($ue) {
- if (!enrol_is_enabled('meta')) {
- // no more enrolments for disabled plugins
- return true;
- }
-
- if ($ue->enrol === 'meta') {
- // prevent circular dependencies - we can not sync meta enrolments recursively
- return true;
- }
-
- self::sync_course_instances($ue->courseid, $ue->userid);
-
- return true;
- }
-
- /**
- * Triggered via user unenrolled event.
- * @static
- * @param stdClass $ue
- * @return bool success
- */
- public static function user_unenrolled($ue) {
- if (!enrol_is_enabled('meta')) {
- // This is slow, let enrol_meta_sync() deal with disabled plugin.
- return true;
- }
-
- if ($ue->enrol === 'meta') {
- // prevent circular dependencies - we can not sync meta enrolments recursively
- return true;
- }
-
- self::sync_course_instances($ue->courseid, $ue->userid);
-
- return true;
- }
-
- /**
- * Triggered via user enrolment modification event.
- * @static
- * @param stdClass $ue
- * @return bool success
- */
- public static function user_enrol_modified($ue) {
- if (!enrol_is_enabled('meta')) {
- // no modifications if plugin disabled
- return true;
- }
-
- if ($ue->enrol === 'meta') {
- // prevent circular dependencies - we can not sync meta enrolments recursively
- return true;
- }
-
- self::sync_course_instances($ue->courseid, $ue->userid);
-
- return true;
- }
-
- /**
- * Triggered via course_deleted event.
- * @static
- * @param stdClass $course
- * @return bool success
- */
- public static function course_deleted($course) {
- global $DB;
-
- if (!enrol_is_enabled('meta')) {
- // This is slow, let enrol_meta_sync() deal with disabled plugin.
- return true;
- }
-
- // does anything want to sync with this parent?
- if (!$enrols = $DB->get_records('enrol', array('customint1'=>$course->id, 'enrol'=>'meta'), 'courseid ASC, id ASC')) {
- return true;
- }
-
- $plugin = enrol_get_plugin('meta');
- $unenrolaction = $plugin->get_config('unenrolaction', ENROL_EXT_REMOVED_SUSPENDNOROLES);
-
- if ($unenrolaction == ENROL_EXT_REMOVED_UNENROL) {
- // Simple, just delete this instance which purges all enrolments,
- // admins were warned that this is risky setting!
- foreach ($enrols as $enrol) {
- $plugin->delete_instance($enrol);
- }
- return true;
- }
-
- foreach ($enrols as $enrol) {
- $enrol->customint = 0;
- $DB->update_record('enrol', $enrol);
-
- if ($unenrolaction == ENROL_EXT_REMOVED_SUSPEND or $unenrolaction == ENROL_EXT_REMOVED_SUSPENDNOROLES) {
- // This makes all enrolments suspended very quickly.
- $plugin->update_status($enrol, ENROL_INSTANCE_DISABLED);
- }
- if ($unenrolaction == ENROL_EXT_REMOVED_SUSPENDNOROLES) {
- $context = context_course::instance($enrol->courseid);
- role_unassign_all(array('contextid'=>$context->id, 'component'=>'enrol_meta', 'itemid'=>$enrol->id));
- }
- }
-
- return true;
- }
}
-
/**
* Sync all meta course links.
*
delete_course($course4, false);
}
+
+ /**
+ * Test user_enrolment_created event.
+ */
+ public function test_user_enrolment_created_observer() {
+ global $DB;
+
+ $this->resetAfterTest();
+
+ $metaplugin = enrol_get_plugin('meta');
+ $user1 = $this->getDataGenerator()->create_user();
+ $course1 = $this->getDataGenerator()->create_course();
+ $course2 = $this->getDataGenerator()->create_course();
+ $student = $DB->get_record('role', array('shortname' => 'student'));
+
+ $e1 = $metaplugin->add_instance($course2, array('customint1' => $course1->id));
+ $enrol1 = $DB->get_record('enrol', array('id' => $e1));
+
+ // Enrol user and capture event.
+ $sink = $this->redirectEvents();
+
+ $metaplugin->enrol_user($enrol1, $user1->id, $student->id);
+ $events = $sink->get_events();
+ $sink->close();
+ $event = array_shift($events);
+
+ // Test Event.
+ $dbuserenrolled = $DB->get_record('user_enrolments', array('userid' => $user1->id));
+ $this->assertInstanceOf('\core\event\user_enrolment_created', $event);
+ $this->assertEquals($dbuserenrolled->id, $event->objectid);
+ $this->assertEquals('user_enrolled', $event->get_legacy_eventname());
+ $expectedlegacyeventdata = $dbuserenrolled;
+ $expectedlegacyeventdata->enrol = 'meta';
+ $expectedlegacyeventdata->courseid = $course2->id;
+ $this->assertEventLegacyData($expectedlegacyeventdata, $event);
+ }
+
+ /**
+ * Test user_enrolment_deleted observer.
+ */
+ public function test_user_enrolment_deleted_observer() {
+ global $DB;
+
+ $this->resetAfterTest(true);
+
+ $metalplugin = enrol_get_plugin('meta');
+ $user1 = $this->getDataGenerator()->create_user();
+ $course1 = $this->getDataGenerator()->create_course();
+ $course2 = $this->getDataGenerator()->create_course();
+ $student = $DB->get_record('role', array('shortname'=>'student'));
+
+ $e1 = $metalplugin->add_instance($course2, array('customint1' => $course1->id));
+ $enrol1 = $DB->get_record('enrol', array('id' => $e1));
+
+ // Enrol user.
+ $metalplugin->enrol_user($enrol1, $user1->id, $student->id);
+ $this->assertEquals(1, $DB->count_records('user_enrolments'));
+
+ // Unenrol user and capture event.
+ $sink = $this->redirectEvents();
+ $metalplugin->unenrol_user($enrol1, $user1->id);
+ $events = $sink->get_events();
+ $sink->close();
+ $event = array_pop($events);
+
+ $this->assertEquals(0, $DB->count_records('user_enrolments'));
+ $this->assertInstanceOf('\core\event\user_enrolment_deleted', $event);
+ $this->assertEquals('user_unenrolled', $event->get_legacy_eventname());
+ }
+
+ /**
+ * Test user_enrolment_updated event.
+ */
+ public function test_user_enrolment_updated_observer() {
+ global $DB;
+
+ $this->resetAfterTest(true);
+
+ $metalplugin = enrol_get_plugin('meta');
+ $user1 = $this->getDataGenerator()->create_user();
+ $course1 = $this->getDataGenerator()->create_course();
+ $course2 = $this->getDataGenerator()->create_course();
+ $student = $DB->get_record('role', array('shortname'=>'student'));
+
+ $e1 = $metalplugin->add_instance($course2, array('customint1' => $course1->id));
+ $enrol1 = $DB->get_record('enrol', array('id' => $e1));
+
+ // Enrol user.
+ $metalplugin->enrol_user($enrol1, $user1->id, $student->id);
+ $this->assertEquals(1, $DB->count_records('user_enrolments'));
+
+ // Updated enrolment for user and capture event.
+ $sink = $this->redirectEvents();
+ $metalplugin->update_user_enrol($enrol1, $user1->id, ENROL_USER_SUSPENDED, null, time());
+ $events = $sink->get_events();
+ $sink->close();
+ $event = array_shift($events);
+
+ // Test Event.
+ $dbuserenrolled = $DB->get_record('user_enrolments', array('userid' => $user1->id));
+ $this->assertInstanceOf('\core\event\user_enrolment_updated', $event);
+ $this->assertEquals($dbuserenrolled->id, $event->objectid);
+ $this->assertEquals('user_enrol_modified', $event->get_legacy_eventname());
+ $expectedlegacyeventdata = $dbuserenrolled;
+ $expectedlegacyeventdata->enrol = 'meta';
+ $expectedlegacyeventdata->courseid = $course2->id;
+ $this->assertEventLegacyData($expectedlegacyeventdata, $event);
+ }
}
// It should be course 1.
$this->assertEquals($sharedcourse->id, $course1->id);
}
+
+ /**
+ * Test user enrolment created event.
+ */
+ public function test_user_enrolment_created_event() {
+ global $DB;
+
+ $this->resetAfterTest();
+
+ $studentrole = $DB->get_record('role', array('shortname'=>'student'));
+ $this->assertNotEmpty($studentrole);
+
+ $admin = get_admin();
+
+ $course1 = $this->getDataGenerator()->create_course();
+
+ $maninstance1 = $DB->get_record('enrol', array('courseid'=>$course1->id, 'enrol'=>'manual'), '*', MUST_EXIST);
+
+ $manual = enrol_get_plugin('manual');
+ $this->assertNotEmpty($manual);
+
+ // Enrol user and capture event.
+ $sink = $this->redirectEvents();
+ $manual->enrol_user($maninstance1, $admin->id, $studentrole->id);
+ $events = $sink->get_events();
+ $sink->close();
+ $event = array_shift($events);
+
+ $dbuserenrolled = $DB->get_record('user_enrolments', array('userid' => $admin->id));
+ $this->assertInstanceOf('\core\event\user_enrolment_created', $event);
+ $this->assertEquals($dbuserenrolled->id, $event->objectid);
+ $this->assertEquals('user_enrolled', $event->get_legacy_eventname());
+ $expectedlegacyeventdata = $dbuserenrolled;
+ $expectedlegacyeventdata->enrol = $manual->get_name();
+ $expectedlegacyeventdata->courseid = $course1->id;
+ $this->assertEventLegacyData($expectedlegacyeventdata, $event);
+ }
}
<div class="fp-navbar">
<div class="filemanager-toolbar">
<div class="fp-toolbar">
- <div class="{!}fp-btn-add"><a href="#"><img src="'.$this->pix_url('a/add_file').'" /> '.$straddfile.'</a></div>
- <div class="{!}fp-btn-mkdir"><a href="#"><img src="'.$this->pix_url('a/create_folder').'" /> '.$strmakedir.'</a></div>
- <div class="{!}fp-btn-download"><a href="#"><img src="'.$this->pix_url('a/download_all').'" /> '.$strdownload.'</a></div>
+ <div class="{!}fp-btn-add"><a role="button" href="#"><img src="'.$this->pix_url('a/add_file').'" /> '.$straddfile.'</a></div>
+ <div class="{!}fp-btn-mkdir"><a role="button" href="#"><img src="'.$this->pix_url('a/create_folder').'" /> '.$strmakedir.'</a></div>
+ <div class="{!}fp-btn-download"><a role="button" href="#"><img src="'.$this->pix_url('a/download_all').'" /> '.$strdownload.'</a></div>
</div>
<div class="{!}fp-viewbar">
<a title="'. get_string('displayicons', 'repository') .'" class="{!}fp-vb-icons" href="#"></a>
*/
private function fp_js_template_generallayout() {
$rv = '
-<div class="file-picker fp-generallayout">
+<div tabindex="0" class="file-picker fp-generallayout" role="dialog" aria-live="assertive">
<div class="fp-repo-area">
<ul class="fp-list">
- <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>
+ <li class="{!}fp-repo"><a href="#"><img class="{!}fp-repo-icon" alt=" " width="16" height="16" /> <span class="{!}fp-repo-name"></span></a></li>
</ul>
</div>
<div class="fp-repo-items" tabindex="0">
$name .= ' ('.get_string('feedback').')';
}
- return strip_tags($name);
+ return html_to_text($name, 0, false);
}
/**
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
- * External assign API
+ * External grading API
*
- * @package core_grade
+ * @package core_grading
* @since Moodle 2.5
* @copyright 2013 Paul Charsley
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
require_once("$CFG->dirroot/grade/grading/lib.php");
/**
- * core grade functions
+ * core grading functions
*/
-class core_grade_external extends external_api {
+class core_grading_external extends external_api {
/**
* Describes the parameters for get_definitions
* @return external_function_parameters
* @since Moodle 2.5
*/
- public static function get_definitions_parameters () {
+ public static function get_definitions_parameters() {
return new external_function_parameters(
array(
'cmids' => new external_multiple_structure(
* @return array of areas with definitions for each requested course module id
* @since Moodle 2.5
*/
- public static function get_definitions ($cmids, $areaname, $activeonly = false) {
+ public static function get_definitions($cmids, $areaname, $activeonly = false) {
global $DB, $CFG;
require_once("$CFG->dirroot/grade/grading/form/lib.php");
$params = self::validate_parameters(self::get_definitions_parameters(),
return $methods;
}
+ /**
+ * Describes the parameters for get_gradingform_instances
+ *
+ * @return external_function_parameters
+ * @since Moodle 2.6
+ */
+ public static function get_gradingform_instances_parameters() {
+ return new external_function_parameters(
+ array(
+ 'definitionid' => new external_value(PARAM_INT, 'definition id'),
+ 'since' => new external_value(PARAM_INT, 'submitted since', VALUE_DEFAULT, 0)
+ )
+ );
+ }
+
+ /**
+ * Returns the instances and fillings for the requested definition id
+ *
+ * @param int $definitionid
+ * @param int $since only return instances with timemodified >= since
+ * @return array of grading instances with fillings for the definition id
+ * @since Moodle 2.6
+ */
+ public static function get_gradingform_instances($definitionid, $since = 0) {
+ global $DB, $CFG;
+ require_once("$CFG->dirroot/grade/grading/form/lib.php");
+ $params = self::validate_parameters(self::get_gradingform_instances_parameters(),
+ array('definitionid' => $definitionid,
+ 'since' => $since));
+ $instances = array();
+ $warnings = array();
+
+ $definition = $DB->get_record('grading_definitions',
+ array('id' => $params['definitionid']),
+ 'areaid,method', MUST_EXIST);
+ $area = $DB->get_record('grading_areas',
+ array('id' => $definition->areaid),
+ 'contextid,component', MUST_EXIST);
+
+ $context = context::instance_by_id($area->contextid);
+ require_capability('moodle/grade:managegradingforms', $context);
+
+ $gradingmanager = get_grading_manager($definition->areaid);
+ $controller = $gradingmanager->get_controller($definition->method);
+ $activeinstances = $controller->get_all_active_instances ($params['since']);
+ $details = $controller->get_external_instance_filling_details();
+ if ($details == null) {
+ $warnings[] = array(
+ 'item' => 'definition',
+ 'itemid' => $params['definitionid'],
+ 'message' => 'Fillings unavailable because get_external_instance_filling_details is not defined',
+ 'warningcode' => '1'
+ );
+ }
+ $getfilling = null;
+ if (method_exists('gradingform_'.$definition->method.'_instance', 'get_'.$definition->method.'_filling')) {
+ $getfilling = 'get_'.$definition->method.'_filling';
+ } else {
+ $warnings[] = array(
+ 'item' => 'definition',
+ 'itemid' => $params['definitionid'],
+ 'message' => 'Fillings unavailable because get_'.$definition->method.'_filling is not defined',
+ 'warningcode' => '1'
+ );
+ }
+ foreach ($activeinstances as $activeinstance) {
+ $instance = array();
+ $instance['id'] = $activeinstance->get_id();
+ $instance['raterid'] = $activeinstance->get_data('raterid');
+ $instance['itemid'] = $activeinstance->get_data('itemid');
+ $instance['rawgrade'] = $activeinstance->get_data('rawgrade');
+ $instance['status'] = $activeinstance->get_data('status');
+ $instance['feedback'] = $activeinstance->get_data('feedback');
+ $instance['feedbackformat'] = $activeinstance->get_data('feedbackformat');
+ // Format the feedback text field.
+ $formattedtext = external_format_text($activeinstance->get_data('feedback'),
+ $activeinstance->get_data('feedbackformat'),
+ $context->id,
+ $area->component,
+ 'feedback',
+ $params['definitionid']);
+ $instance['feedback'] = $formattedtext[0];
+ $instance['feedbackformat'] = $formattedtext[1];
+ $instance['timemodified'] = $activeinstance->get_data('timemodified');
+
+ if ($details != null && $getfilling != null) {
+ $fillingdata = $activeinstance->$getfilling();
+ $filling = array();
+ foreach ($details as $key => $value) {
+ $filling[$key] = self::format_text($fillingdata[$key],
+ $context->id,
+ $area->component,
+ $params['definitionid']);
+ }
+ $instance[$definition->method] = $filling;
+ }
+ $instances[] = $instance;
+ }
+ $result = array(
+ 'instances' => $instances,
+ 'warnings' => $warnings
+ );
+ return $result;
+ }
+
+ /**
+ * Creates a grading instance
+ *
+ * @return external_single_structure
+ * @since Moodle 2.6
+ */
+ private static function grading_instance() {
+ global $CFG;
+ $instance = array();
+ $instance['id'] = new external_value(PARAM_INT, 'instance id');
+ $instance['raterid'] = new external_value(PARAM_INT, 'rater id');
+ $instance['itemid'] = new external_value(PARAM_INT, 'item id');
+ $instance['rawgrade'] = new external_value(PARAM_TEXT, 'raw grade', VALUE_OPTIONAL);
+ $instance['status'] = new external_value(PARAM_INT, 'status');
+ $instance['feedback'] = new external_value(PARAM_RAW, 'feedback', VALUE_OPTIONAL);
+ $instance['feedbackformat'] = new external_format_value('feedback', VALUE_OPTIONAL);
+ $instance['timemodified'] = new external_value(PARAM_INT, 'modified time');
+ foreach (self::get_grading_methods() as $method) {
+ require_once($CFG->dirroot.'/grade/grading/form/'.$method.'/lib.php');
+ $details = call_user_func('gradingform_'.$method.'_controller::get_external_instance_filling_details');
+ if ($details != null) {
+ $items = array();
+ foreach ($details as $key => $value) {
+ $details[$key]->required = VALUE_OPTIONAL;
+ $items[$key] = $value;
+ }
+ $instance[$method] = new external_single_structure($items, 'items', VALUE_OPTIONAL);
+ }
+ }
+ return new external_single_structure($instance);
+ }
+
+ /**
+ * Describes the get_gradingform_instances return value
+ *
+ * @return external_single_structure
+ * @since Moodle 2.6
+ */
+ public static function get_gradingform_instances_returns() {
+ return new external_single_structure(
+ array(
+ 'instances' => new external_multiple_structure(self::grading_instance(), 'list of grading instances'),
+ 'warnings' => new external_warnings()
+ )
+ );
+ }
+
+}
+
+/**
+ * core grading functions. Renamed to core_grading_external
+ *
+ * @since Moodle 2.5
+ * @deprecated since 2.6 See MDL-30085. Please do not use this class any more.
+ * @see core_grading_external
+ */
+class core_grade_external extends external_api {
+
+ public static function get_definitions_parameters() {
+ return core_grading_external::get_definitions_parameters();
+ }
+
+ public static function get_definitions($cmids, $areaname, $activeonly = false) {
+ return core_grading_external::get_definitions($cmids, $areaname, $activeonly = false);
+ }
+
+ public static function get_definitions_returns() {
+ return core_grading_external::get_definitions_returns();
+ }
+
}
);
return array('guide_criteria' => $guide_criteria, 'guide_comment' => $guide_comment);
}
+
+ /**
+ * Returns an array that defines the structure of the guide's filling. This function is used by
+ * the web service function core_grading_external::get_gradingform_instances().
+ *
+ * @return An array containing a single key/value pair with the 'criteria' external_multiple_structure
+ * @see gradingform_controller::get_external_instance_filling_details()
+ * @since Moodle 2.6
+ */
+ public static function get_external_instance_filling_details() {
+ $criteria = new external_multiple_structure(
+ new external_single_structure(
+ array(
+ 'id' => new external_value(PARAM_INT, 'filling id'),
+ 'criterionid' => new external_value(PARAM_INT, 'criterion id'),
+ 'levelid' => new external_value(PARAM_INT, 'level id', VALUE_OPTIONAL),
+ 'remark' => new external_value(PARAM_RAW, 'remark', VALUE_OPTIONAL),
+ 'remarkformat' => new external_format_value('remark', VALUE_OPTIONAL),
+ 'score' => new external_value(PARAM_FLOAT, 'maximum score')
+ )
+ ), 'filling', VALUE_OPTIONAL
+ );
+ return array ('criteria' => $criteria);
+ }
+
}
/**
return $rv;
}
+ /**
+ * Returns an array of all active instances for this definition.
+ * (intentionally does not return instances with status NEEDUPDATE)
+ *
+ * @param int since only return instances with timemodified >= since
+ * @return array of gradingform_instance objects
+ */
+ public function get_all_active_instances($since = 0) {
+ global $DB;
+ $conditions = array ($this->definition->id,
+ gradingform_instance::INSTANCE_STATUS_ACTIVE,
+ $since);
+ $where = "definitionid = ? AND status = ? AND timemodified >= ?";
+ $records = $DB->get_records_select('grading_instances', $where, $conditions);
+ $rv = array();
+ foreach ($records as $record) {
+ $rv[] = $this->get_instance($record);
+ }
+ return $rv;
+ }
+
/**
* Returns true if there are already people who has been graded on this definition.
* In this case plugins may restrict changes of the grading definition
public static function get_external_definition_details() {
return null;
}
+
+ /**
+ * Overridden by sub classes that wish to make instance filling details available to web services.
+ * When not overridden, only instance filling data common to all grading methods is made available.
+ * When overriding, the return value should be an array containing one or more key/value pairs.
+ * These key/value pairs should match the filling data returned by the get_<method>_filling() function
+ * in the gradingform_instance subclass.
+ * For examples, look at:
+ * $gradingform_rubric_controller->get_external_instance_filling_details()
+ * $gradingform_guide_controller->get_external_instance_filling_details()
+ *
+ * @return array An array of one or more key/value pairs containing the external_multiple_structure/s
+ * corresponding to the definition returned by $gradingform_<method>_instance->get_<method>_filling()
+ * @since Moodle 2.6
+ */
+ public static function get_external_instance_filling_details() {
+ return null;
+ }
}
/**
public function default_validation_error_message() {
return '';
}
-}
\ No newline at end of file
+}
return array('rubric_criteria' => $rubric_criteria);
}
+ /**
+ * Returns an array that defines the structure of the rubric's filling. This function is used by
+ * the web service function core_grading_external::get_gradingform_instances().
+ *
+ * @return An array containing a single key/value pair with the 'criteria' external_multiple_structure
+ * @see gradingform_controller::get_external_instance_filling_details()
+ * @since Moodle 2.6
+ */
+ public static function get_external_instance_filling_details() {
+ $criteria = new external_multiple_structure(
+ new external_single_structure(
+ array(
+ 'id' => new external_value(PARAM_INT, 'filling id'),
+ 'criterionid' => new external_value(PARAM_INT, 'criterion id'),
+ 'levelid' => new external_value(PARAM_INT, 'level id', VALUE_OPTIONAL),
+ 'remark' => new external_value(PARAM_RAW, 'remark', VALUE_OPTIONAL),
+ 'remarkformat' => new external_format_value('remark', VALUE_OPTIONAL)
+ )
+ ), 'filling', VALUE_OPTIONAL
+ );
+ return array ('criteria' => $criteria);
+ }
+
}
/**
require_once($CFG->dirroot . '/webservice/tests/helpers.php');
/**
- * External core grade functions unit tests
+ * Unit tests for the grading API at /grade/externallib.php
*
- * @package core_grade
+ * @package core_grading
* @category external
* @copyright 2013 Paul Charsley
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-class core_grade_externallib_testcase extends externallib_advanced_testcase {
+class core_grading_externallib_testcase extends externallib_advanced_testcase {
/**
* Tests set up
/**
* Test get_definitions
*/
- public function test_get_definitions () {
+ public function test_get_definitions() {
global $DB, $CFG, $USER;
$this->resetAfterTest(true);
$cm = self::getDataGenerator()->create_module('assign', $assigndata);
// Create manual enrolment record.
- $manual_enrol_data['enrol'] = 'manual';
- $manual_enrol_data['status'] = 0;
- $manual_enrol_data['courseid'] = $course->id;
- $enrolid = $DB->insert_record('enrol', $manual_enrol_data);
+ $manualenroldata['enrol'] = 'manual';
+ $manualenroldata['status'] = 0;
+ $manualenroldata['courseid'] = $course->id;
+ $enrolid = $DB->insert_record('enrol', $manualenroldata);
// Create a teacher and give them capabilities.
$coursecontext = context_course::instance($course->id);
$this->assignUserCapability('mod/assign:grade', $modulecontext->id, $roleid);
// Create the teacher's enrolment record.
- $user_enrolment_data['status'] = 0;
- $user_enrolment_data['enrolid'] = $enrolid;
- $user_enrolment_data['userid'] = $USER->id;
- $DB->insert_record('user_enrolments', $user_enrolment_data);
+ $userenrolmentdata['status'] = 0;
+ $userenrolmentdata['enrolid'] = $enrolid;
+ $userenrolmentdata['userid'] = $USER->id;
+ $DB->insert_record('user_enrolments', $userenrolmentdata);
// Create a grading area.
$gradingarea = array(
// Call the external function.
$cmids = array ($cm->id);
$areaname = 'submissions';
- $result = core_grade_external::get_definitions($cmids, $areaname);
+ $result = core_grading_external::get_definitions($cmids, $areaname);
$this->assertEquals(1, count($result['areas']));
$this->assertEquals(1, count($result['areas'][0]['definitions']));
$this->assertTrue($found);
}
+ /**
+ * Test get_gradingform_instances
+ */
+ public function test_get_gradingform_instances() {
+ global $DB, $USER;
+
+ $this->resetAfterTest(true);
+ // Create a course and assignment.
+ $coursedata['idnumber'] = 'idnumbercourse';
+ $coursedata['fullname'] = 'Lightwork Course';
+ $coursedata['summary'] = 'Lightwork Course description';
+ $coursedata['summaryformat'] = FORMAT_MOODLE;
+ $course = self::getDataGenerator()->create_course($coursedata);
+
+ $assigndata['course'] = $course->id;
+ $assigndata['name'] = 'lightwork assignment';
+
+ $assign = self::getDataGenerator()->create_module('assign', $assigndata);
+
+ // Create manual enrolment record.
+ $manualenroldata['enrol'] = 'manual';
+ $manualenroldata['status'] = 0;
+ $manualenroldata['courseid'] = $course->id;
+ $enrolid = $DB->insert_record('enrol', $manualenroldata);
+
+ // Create a teacher and give them capabilities.
+ $coursecontext = context_course::instance($course->id);
+ $roleid = $this->assignUserCapability('moodle/course:viewparticipants', $coursecontext->id, 3);
+ $modulecontext = context_module::instance($assign->id);
+ $this->assignUserCapability('mod/assign:grade', $modulecontext->id, $roleid);
+
+ // Create the teacher's enrolment record.
+ $userenrolmentdata['status'] = 0;
+ $userenrolmentdata['enrolid'] = $enrolid;
+ $userenrolmentdata['userid'] = $USER->id;
+ $DB->insert_record('user_enrolments', $userenrolmentdata);
+
+ // Create a student with an assignment grade.
+ $student = self::getDataGenerator()->create_user();
+ $assigngrade = new stdClass();
+ $assigngrade->assignment = $assign->id;
+ $assigngrade->userid = $student->id;
+ $assigngrade->timecreated = time();
+ $assigngrade->timemodified = $assigngrade->timecreated;
+ $assigngrade->grader = $USER->id;
+ $assigngrade->grade = 50;
+ $assigngrade->attemptnumber = 0;
+ $gid = $DB->insert_record('assign_grades', $assigngrade);
+
+ // Create a grading area.
+ $gradingarea = array(
+ 'contextid' => $modulecontext->id,
+ 'component' => 'mod_assign',
+ 'areaname' => 'submissions',
+ 'activemethod' => 'rubric'
+ );
+ $areaid = $DB->insert_record('grading_areas', $gradingarea);
+
+ // Create a rubric grading definition.
+ $rubricdefinition = array (
+ 'areaid' => $areaid,
+ 'method' => 'rubric',
+ 'name' => 'test',
+ 'status' => 20,
+ 'copiedfromid' => 1,
+ 'timecreated' => 1,
+ 'usercreated' => $USER->id,
+ 'timemodified' => 1,
+ 'usermodified' => $USER->id,
+ 'timecopied' => 0
+ );
+ $definitionid = $DB->insert_record('grading_definitions', $rubricdefinition);
+
+ // Create a criterion with a level.
+ $rubriccriteria = array (
+ 'definitionid' => $definitionid,
+ 'sortorder' => 1,
+ 'description' => 'Demonstrate an understanding of disease control',
+ 'descriptionformat' => 0
+ );
+ $criterionid = $DB->insert_record('gradingform_rubric_criteria', $rubriccriteria);
+ $rubriclevel = array (
+ 'criterionid' => $criterionid,
+ 'score' => 50,
+ 'definition' => 'pass',
+ 'definitionformat' => 0
+ );
+ $levelid = $DB->insert_record('gradingform_rubric_levels', $rubriclevel);
+
+ // Create a grading instance.
+ $instance = array (
+ 'definitionid' => $definitionid,
+ 'raterid' => $USER->id,
+ 'itemid' => $gid,
+ 'status' => 1,
+ 'feedbackformat' => 0,
+ 'timemodified' => 1
+ );
+ $instanceid = $DB->insert_record('grading_instances', $instance);
+
+ // Create a filling.
+ $filling = array (
+ 'instanceid' => $instanceid,
+ 'criterionid' => $criterionid,
+ 'levelid' => $levelid,
+ 'remark' => 'excellent work',
+ 'remarkformat' => 0
+ );
+ $DB->insert_record('gradingform_rubric_fillings', $filling);
+
+ // Call the external function.
+ $result = core_grading_external::get_gradingform_instances($definitionid, 0);
+
+ $this->assertEquals(1, count($result['instances']));
+ $this->assertEquals($USER->id, $result['instances'][0]['raterid']);
+ $this->assertEquals($gid, $result['instances'][0]['itemid']);
+ $this->assertEquals(1, $result['instances'][0]['status']);
+ $this->assertEquals(1, $result['instances'][0]['timemodified']);
+ $this->assertEquals(1, count($result['instances'][0]['rubric']));
+ $this->assertEquals(1, count($result['instances'][0]['rubric']['criteria']));
+ $criteria = $result['instances'][0]['rubric']['criteria'];
+ $this->assertEquals($criterionid, $criteria[$criterionid]['criterionid']);
+ $this->assertEquals($levelid, $criteria[$criterionid]['levelid']);
+ $this->assertEquals('excellent work', $criteria[$criterionid]['remark']);
+ }
}
You can still control individual badge privacy settings on your "My badges" page.';
$string['badgeprivacysetting_str'] = 'Automatically show badges I earn on my profile page';
$string['badgesalt'] = 'Salt for hashing the recepient\'s email address';
-$string['badgesalt_desc'] = 'Using a hash allows backpack services to confirm the badge earner without having to expose their email address. This setting should only use numbers and letters.';
+$string['badgesalt_desc'] = 'Using a hash allows backpack services to confirm the badge earner without having to expose their email address. This setting should only use numbers and letters.
+
+Note: For recipient verification purposes, please avoid changing this setting once you start issuing badges.';
$string['badgesdisabled'] = 'Badges are not enabled on this site.';
$string['badgesearned'] = 'Number of badges earned: {$a}';
$string['badgesettings'] = 'Badges settings';
$string['error:cannotawardbadge'] = 'Cannot award badge to a user.';
$string['error:clone'] = 'Cannot clone the badge.';
$string['error:duplicatename'] = 'Badge with such name already exists in the system.';
+$string['error:externalbadgedoesntexist'] = 'Badge not found';
$string['error:invalidbadgeurl'] = 'Invalid badge issuer URL format.';
$string['error:invalidcriteriatype'] = 'Invalid criteria type.';
$string['error:invalidexpiredate'] = 'Expiry date has to be in the future.';
$string['overallcrit'] = 'of the selected criteria are complete.';
$string['potentialrecipients'] = 'Potential badge recipients';
$string['recipients'] = 'Badge recipients';
+$string['recipientdetails'] = 'Recipient details';
+$string['recipientidentificationproblem'] = 'Cannot find a recipient of this badge among the existing users.';
+$string['recipientvalidationproblem'] = 'Current user cannot be verified as a recipient of this badge.';
$string['relative'] = 'Relative date';
$string['requiredcourse'] = 'At least one course should be added to the courseset criterion.';
$string['reviewbadge'] = 'Review badge criteria';
$string['cachedef_coursecattree'] = 'Course categories tree';
$string['cachedef_databasemeta'] = 'Database meta information';
$string['cachedef_eventinvalidation'] = 'Event invalidation';
+$string['cachedef_externalbadges'] = 'External badges for particular user';
$string['cachedef_groupdata'] = 'Course group information';
$string['cachedef_htmlpurifier'] = 'HTML Purifier - cleaned content';
$string['cachedef_langmenu'] = 'List of available languages';
$string['errorenrolcohortusers'] = 'Error enrolling cohort members in this course.';
$string['errorthresholdlow'] = 'Notification threshold must be at least 1 day.';
$string['errorwithbulkoperation'] = 'There was an error while processing your bulk enrolment change.';
+$string['eventuserenrolmentcreated'] = 'User enrolled in course';
+$string['eventuserenrolmentdeleted'] = 'User unenrolled from course';
+$string['eventuserenrolmentupdated'] = 'User unenrolment updated';
$string['expirynotify'] = 'Notify before enrolment expires';
$string['expirynotify_help'] = 'This setting determines whether enrolment expiry notification messages are sent.';
$string['expirynotifyall'] = 'Enroller and enrolled user';
$string['courseformatnotfound'] = 'The course format \'{$a}\' doesn\'t exist or is not recognized';
$string['coursegroupunknown'] = 'Course corresponding to group {$a} not specified';
$string['courseidnotfound'] = 'Course id doesn\'t exist';
+$string['courseidnumbertaken'] = 'ID number is already used for another course ({$a})';
$string['coursemisconf'] = 'Course is misconfigured';
$string['courserequestdisabled'] = 'Sorry, but course requests have been disabled by the administrator.';
$string['csvcolumnduplicates'] = 'Duplicate columns detected';
$string['hackdetected'] = 'Hack attack detected!';
$string['hashpoolproblem'] = 'Incorrect pool file content {$a}.';
$string['headersent'] = 'Headers already sent';
-$string['idnumbertaken'] = 'ID number is already used for another course';
+$string['idnumbertaken'] = 'This ID number is already in use';
$string['idnumbertoolong'] = 'ID number is too long';
$string['importformatnotimplement'] = 'Sorry, importing this format is not yet implemented!';
$string['incorrectext'] = 'File has an incorrect extension';
$string['sessionerroruser2'] = 'A server error that affects your login session was detected. Please login again or restart your browser.';
$string['sessionipnomatch'] = 'Sorry, but your IP number seems to have changed from when you first logged in. This security feature prevents crackers stealing your identity while logged in to this site. Normal users should not be seeing this message - please ask the site administrator for help.';
$string['sessionipnomatch2'] = 'Sorry, but your IP number seems to have changed from when you first logged in. This security feature prevents crackers stealing your identity while logged in to this site. You may see this error if you use wireless networks or if you are roaming between different networks. Please ask the site administrator for more help.<br /><br />If you want to continue please press F5 key to refresh this page.';
-$string['shortnametaken'] = 'Short name is already used for another course';
+$string['shortnametaken'] = 'Short name is already used for another course ({$a})';
$string['scheduledbackupsdisabled'] = 'Scheduled backups have been disabled by the server admin';
$string['socksnotsupported'] = 'SOCKS5 proxy is not supported in PHP4';
$string['spellcheckernotconf'] = 'Spellchecker not configured';
If you use this area to store course files, you can expose yourself to a number of privacy and security issues, as well as experiencing missing files in backups, course imports and any time content is shared or re-used. It is therefore recommended that you do not use this area unless you really know what you are doing.';
$string['courselegacyfiles_link'] = 'coursefiles2';
+$string['courselegacyfilesofcourse'] = 'Legacy course files: {$a}';
$string['courseoverview'] = 'Course overview';
$string['courseoverviewgraph'] = 'Course overview graph';
$string['courseprofiles'] = 'Course profiles';
$string['eventcourserestored'] = 'Course restored';
$string['eventcourseupdated'] = 'Course updated';
$string['eventcoursesectionupdated'] = ' Course section updated';
+$string['eventusercreated'] = 'User created';
+$string['eventuserdeleted'] = 'User deleted';
+$string['eventuserloggedout'] = 'User logged out';
+$string['eventuserupdated'] = 'User updated';
$string['everybody'] = 'Everybody';
$string['executeat'] = 'Execute at';
$string['existing'] = 'Existing';
/**
* @var array An array mapping context levels to classes
*/
- private static $alllevels = array(
+ private static $alllevels;
+
+ /**
+ * Instance does not make sense here, only static use
+ */
+ protected function __construct() {
+ }
+
+ /**
+ * Initialise context levels, call before using self::$alllevels.
+ */
+ private static function init_levels() {
+ global $CFG;
+
+ if (isset(self::$alllevels)) {
+ return;
+ }
+ self::$alllevels = array(
CONTEXT_SYSTEM => 'context_system',
CONTEXT_USER => 'context_user',
CONTEXT_COURSECAT => 'context_coursecat',
CONTEXT_COURSE => 'context_course',
CONTEXT_MODULE => 'context_module',
CONTEXT_BLOCK => 'context_block',
- );
+ );
- /**
- * Instance does not make sense here, only static use
- */
- protected function __construct() {
+ if (empty($CFG->custom_context_classes)) {
+ return;
+ }
+
+ // Unsupported custom levels, use with care!!!
+ foreach ($CFG->custom_context_classes as $level => $classname) {
+ self::$alllevels[$level] = $classname;
+ }
+ ksort(self::$alllevels);
}
/**
* @return string class name of the context class
*/
public static function get_class_for_level($contextlevel) {
+ self::init_levels();
if (isset(self::$alllevels[$contextlevel])) {
return self::$alllevels[$contextlevel];
} else {
* @return array int=>string (level=>level class name)
*/
public static function get_all_levels() {
+ self::init_levels();
return self::$alllevels;
}
*/
public static function cleanup_instances() {
global $DB;
+ self::init_levels();
+
$sqls = array();
foreach (self::$alllevels as $level=>$classname) {
$sqls[] = $classname::get_cleanup_sql();
* @return void
*/
public static function create_instances($contextlevel = null, $buildpaths = true) {
+ self::init_levels();
foreach (self::$alllevels as $level=>$classname) {
if ($contextlevel and $level > $contextlevel) {
// skip potential sub-contexts
* @return void
*/
public static function build_all_paths($force = false) {
+ self::init_levels();
foreach (self::$alllevels as $classname) {
$classname::build_paths($force);
}
}
}
-/**
- * Triggered when 'user_updated' event happens.
- *
- * @param object $eventdata Holds all information about a user.
- * @return boolean
- */
-function badges_award_handle_profile_criteria_review(stdClass $eventdata) {
- global $DB, $CFG;
-
- if (!empty($CFG->enablebadges)) {
- $userid = $eventdata->id;
-
- if ($rs = $DB->get_records('badge_criteria', array('criteriatype' => BADGE_CRITERIA_TYPE_PROFILE))) {
- foreach ($rs as $r) {
- $badge = new badge($r->badgeid);
- if (!$badge->is_active() || $badge->is_issued($userid)) {
- continue;
- }
-
- if ($badge->criteria[BADGE_CRITERIA_TYPE_PROFILE]->review($userid)) {
- $badge->criteria[BADGE_CRITERIA_TYPE_PROFILE]->mark_complete($userid);
-
- if ($badge->criteria[BADGE_CRITERIA_TYPE_OVERALL]->review($userid)) {
- $badge->criteria[BADGE_CRITERIA_TYPE_OVERALL]->mark_complete($userid);
- $badge->issue($userid);
- }
- }
- }
- }
- }
-
- return true;
-}
-
/**
* Triggered when badge is manually awarded.
*
/**
* Returns external backpack settings and badges from this backpack.
*
+ * This function first checks if badges for the user are cached and
+ * tries to retrieve them from the cache. Otherwise, badges are obtained
+ * through curl request to the backpack.
+ *
* @param int $userid Backpack user ID.
+ * @param boolean $refresh Refresh badges collection in cache.
* @return null|object Returns null is there is no backpack or object with backpack settings.
*/
-function get_backpack_settings($userid) {
+function get_backpack_settings($userid, $refresh = false) {
global $DB;
require_once(dirname(dirname(__FILE__)) . '/badges/lib/backpacklib.php');
+ // Try to get badges from cache first.
+ $badgescache = cache::make('core', 'externalbadges');
+ $out = $badgescache->get($userid);
+ if ($out !== false && !$refresh) {
+ return $out;
+ }
+ // Get badges through curl request to the backpack.
$record = $DB->get_record('badge_backpack', array('userid' => $userid));
if ($record) {
$backpack = new OpenBadgesBackpackHandler($record);
$out->totalcollections = 0;
}
+ $badgescache->set($userid, $out);
return $out;
}
* @return void
*/
public static function install_site() {
- global $DB;
-
+ global $DB, $CFG;
+ require_once($CFG->dirroot.'/user/lib.php');
if (!defined('BEHAT_UTIL')) {
throw new coding_exception('This method can be only used by Behat CLI tool');
}
$user->lastname = 'User';
$user->city = 'Perth';
$user->country = 'AU';
- $DB->update_record('user', $user);
+ user_update_user($user, false);
// Disable email message processor.
$DB->set_field('message_processors', 'enabled', '0', array('name' => 'email'));
*
* @param string $region The name of the region to check
*/
- protected function ensure_content_created($region, $output) {
+ public function ensure_content_created($region, $output) {
$this->ensure_instances_exist($region);
if (!array_key_exists($region, $this->visibleblockcontent)) {
$contents = array();
* @return string
*/
public static function get_name() {
- return new get_string('eventcoursecompleted', 'core_completion');
+ return get_string('eventcoursecompleted', 'core_completion');
}
/**
* @return string
*/
public static function get_name() {
- return new get_string('eventcoursecompletionupdated', 'core_completion');
+ return get_string('eventcoursecompletionupdated', 'core_completion');
}
/**
* @return string
*/
public static function get_name() {
- return new get_string('eventcoursemodulecompletionupdated', 'core_completion');
+ return get_string('eventcoursemodulecompletionupdated', 'core_completion');
}
/**
--- /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/>.
+
+/**
+ * User created event.
+ *
+ * @package core
+ * @copyright 2013 Rajesh Taneja <rajesh@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace core\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Event when new user profile is created.
+ *
+ * @package core
+ * @copyright 2013 Rajesh Taneja <rajesh@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class user_created extends base {
+
+ /**
+ * Initialise required event data properties.
+ */
+ protected function init() {
+ $this->data['objecttable'] = 'user';
+ $this->data['crud'] = 'c';
+ $this->data['level'] = self::LEVEL_OTHER;
+ }
+
+ /**
+ * Returns localised event name.
+ *
+ * @return string
+ */
+ public static function get_name() {
+ return get_string('eventusercreated');
+ }
+
+ /**
+ * Returns non-localised event description with id's for admin use only.
+ *
+ * @return string
+ */
+ public function get_description() {
+ return 'Profile created for user '.$this->objectid;
+ }
+
+ /**
+ * Returns relevant URL.
+ *
+ * @return \moodle_url
+ */
+ public function get_url() {
+ return new \moodle_url('/user/view.php', array('id' => $this->objectid));
+ }
+
+ /**
+ * Return name of the legacy event, which is replaced by this event.
+ *
+ * @return string legacy event name
+ */
+ public static function get_legacy_eventname() {
+ return 'user_created';
+ }
+
+ /**
+ * Return user_created legacy event data.
+ *
+ * @return \stdClass user data.
+ */
+ protected function get_legacy_eventdata() {
+ return $this->get_record_snapshot('user', $this->objectid);
+ }
+
+ /**
+ * Returns array of parameters to be passed to legacy add_to_log() function.
+ *
+ * @return array
+ */
+ protected function get_legacy_logdata() {
+ return array(SITEID, 'user', 'add', '/view.php?id='.$this->objectid, fullname($this->get_legacy_eventdata()));
+ }
+}
--- /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/>.
+
+/**
+ * User deleted event.
+ *
+ * @package core
+ * @copyright 2013 Rajesh Taneja <rajesh@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace core\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Event when user profile is deleted.
+ *
+ * @package core
+ * @copyright 2013 Rajesh Taneja <rajesh@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class user_deleted extends base {
+
+ /**
+ * Initialise required event data properties.
+ */
+ protected function init() {
+ $this->data['objecttable'] = 'user';
+ $this->data['crud'] = 'd';
+ $this->data['level'] = self::LEVEL_OTHER;
+ }
+
+ /**
+ * Returns localised event name.
+ *
+ * @return string
+ */
+ public static function get_name() {
+ return get_string('eventuserdeleted');
+ }
+
+ /**
+ * Returns non-localised event description with id's for admin use only.
+ *
+ * @return string
+ */
+ public function get_description() {
+ $user = (object)$this->other['user'];
+ return 'User profile deleted for user '.$user->firstname.' '.$user->lastname.' id ('.$user->id.')';
+ }
+
+ /**
+ * Return name of the legacy event, which is replaced by this event.
+ *
+ * @return string legacy event name
+ */
+ public static function get_legacy_eventname() {
+ return 'user_deleted';
+ }
+
+ /**
+ * Return user_deleted legacy event data.
+ *
+ * @return \stdClass user data.
+ */
+ protected function get_legacy_eventdata() {
+ return (object)$this->other['user'];
+ }
+
+ /**
+ * Returns array of parameters to be passed to legacy add_to_log() function.
+ *
+ * @return array
+ */
+ protected function get_legacy_logdata() {
+ $user = (object)$this->other['user'];
+ return array(SITEID, 'user', 'delete', "view.php?id=$user->id", $user->firstname.' '.$user->lastname);
+ }
+
+ /**
+ * Custom validation.
+ *
+ * @throws \coding_exception
+ * @return void
+ */
+ protected function validate_data() {
+ parent::validate_data();
+ if (!isset($this->other['user'])) {
+ throw new \coding_exception('user must be set in $other.');
+ }
+ }
+}
--- /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/>.
+
+/**
+ * User enrolment created event.
+ *
+ * @package core
+ * @copyright 2013 Rajesh Taneja <rajesh@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace core\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Event when user is enrolled in a course.
+ *
+ * @package core
+ * @copyright 2013 Rajesh Taneja <rajesh@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class user_enrolment_created extends base {
+
+ /**
+ * Initialise required event data properties.
+ */
+ protected function init() {
+ $this->data['objecttable'] = 'user_enrolments';
+ $this->data['crud'] = 'c';
+ $this->data['level'] = self::LEVEL_TEACHING;
+ }
+
+ /**
+ * Returns localised event name.
+ *
+ * @return string
+ */
+ public static function get_name() {
+ return get_string('eventuserenrolmentcreated', 'core_enrol');
+ }
+
+ /**
+ * Returns non-localised event description with id's for admin use only.
+ *
+ * @return string
+ */
+ public function get_description() {
+ return 'User '.$this->relateduserid. ' is enrolled in course '.$this->courseid.' by user '.$this->userid;
+ }
+
+ /**
+ * Returns relevant URL.
+ *
+ * @return \moodle_url
+ */
+ public function get_url() {
+ return new \moodle_url('/enrol/users.php', array('id' => $this->courseid));
+ }
+
+ /**
+ * Return name of the legacy event, which is replaced by this event.
+ *
+ * @return string legacy event name
+ */
+ public static function get_legacy_eventname() {
+ return 'user_enrolled';
+ }
+
+ /**
+ * Return user_enrolled legacy event data.
+ *
+ * @return \stdClass
+ */
+ protected function get_legacy_eventdata() {
+ $legacyeventdata = $this->get_record_snapshot('user_enrolments', $this->objectid);
+ $legacyeventdata->enrol = $this->other['enrol'];
+ $legacyeventdata->courseid = $this->courseid;
+ return $legacyeventdata;
+ }
+
+ /**
+ * Custom validation.
+ *
+ * @throws \coding_exception
+ * @return void
+ */
+ protected function validate_data() {
+ parent::validate_data();
+ if (!isset($this->other['enrol'])) {
+ throw new \coding_exception('Enrolment plugin name must be set in $other.');
+ }
+ if (!isset($this->relateduserid)) {
+ throw new \coding_exception('Related user id must be set.');
+ }
+ }
+}
--- /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/>.
+
+/**
+ * User enrolment deleted event.
+ *
+ * @package core
+ * @copyright 2013 Rajesh Taneja <rajesh@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace core\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Event when user is unenrolled from a course.
+ *
+ * @package core
+ * @copyright 2013 Rajesh Taneja <rajesh@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class user_enrolment_deleted extends base {
+
+ /**
+ * Initialise required event data properties.
+ */
+ protected function init() {
+ $this->data['objecttable'] = 'user_enrolments';
+ $this->data['crud'] = 'd';
+ $this->data['level'] = self::LEVEL_TEACHING;
+ }
+
+ /**
+ * Returns localised event name.
+ *
+ * @return string
+ */
+ public static function get_name() {
+ return get_string('eventuserenrolmentdeleted', 'core_enrol');
+ }
+
+ /**
+ * Returns non-localised event description with id's for admin use only.
+ *
+ * @return string
+ */
+ public function get_description() {
+ return 'User '.$this->relateduserid. ' is enrolled in course '.$this->courseid.' by user '.$this->userid;
+ }
+
+ /**
+ * Returns relevant URL.
+ *
+ * @return \moodle_url
+ */
+ public function get_url() {
+ return new \moodle_url('/enrol/users.php', array('id' => $this->courseid));
+ }
+
+ /**
+ * Return name of the legacy event, which is replaced by this event.
+ *
+ * @return string legacy event name
+ */
+ public static function get_legacy_eventname() {
+ return 'user_unenrolled';
+ }
+
+ /**
+ * Return user_unenrolled legacy event data.
+ *
+ * @return \stdClass
+ */
+ protected function get_legacy_eventdata() {
+ return (object)$this->other['userenrolment'];
+ }
+
+
+ /**
+ * Custom validation.
+ *
+ * @throws \coding_exception
+ * @return void
+ */
+ protected function validate_data() {
+ parent::validate_data();
+ if (!isset($this->other['userenrolment'])) {
+ throw new \coding_exception('User enrolment must be set in $other.');
+ }
+ if (!isset($this->other['enrol'])) {
+ throw new \coding_exception('Enrolment plugin name must be set in $other.');
+ }
+ if (!isset($this->relateduserid)) {
+ throw new \coding_exception('Related user id must be set.');
+ }
+ }
+}
--- /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/>.
+
+/**
+ * User enrolment updated event.
+ *
+ * @package core
+ * @copyright 2013 Rajesh Taneja <rajesh@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace core\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Event when user enrolment is updated.
+ *
+ * @package core
+ * @copyright 2013 Rajesh Taneja <rajesh@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class user_enrolment_updated extends base {
+
+ /**
+ * Initialise required event data properties.
+ */
+ protected function init() {
+ $this->data['objecttable'] = 'user_enrolments';
+ $this->data['crud'] = 'u';
+ $this->data['level'] = self::LEVEL_TEACHING;
+ }
+
+ /**
+ * Returns localised event name.
+ *
+ * @return string
+ */
+ public static function get_name() {
+ return get_string('eventuserenrolmentupdated', 'core_enrol');
+ }
+
+ /**
+ * Returns non-localised event description with id's for admin use only.
+ *
+ * @return string
+ */
+ public function get_description() {
+ return 'User '.$this->relateduserid. ' has updated enrolment for user '.$this->userid.' in course '.$this->courseid;
+ }
+
+ /**
+ * Returns relevant URL.
+ *
+ * @return \moodle_url
+ */
+ public function get_url() {
+ return new \moodle_url('/enrol/users.php', array('id' => $this->courseid));
+ }
+
+ /**
+ * Return name of the legacy event, which is replaced by this event.
+ *
+ * @return string legacy event name
+ */
+ public static function get_legacy_eventname() {
+ return 'user_enrol_modified';
+ }
+
+ /**
+ * Return user_enrol_modified legacy event data.
+ *
+ * @return \stdClass
+ */
+ protected function get_legacy_eventdata() {
+ $legacyeventdata = $this->get_record_snapshot('user_enrolments', $this->objectid);
+ $legacyeventdata->enrol = $this->other['enrol'];
+ $legacyeventdata->courseid = $this->courseid;
+ return $legacyeventdata;
+ }
+
+ /**
+ * Custom validation.
+ *
+ * @throws \coding_exception
+ * @return void
+ */
+ protected function validate_data() {
+ parent::validate_data();
+ if (!isset($this->other['enrol'])) {
+ throw new \coding_exception('Enrolment plugin name must be set in $other.');
+ }
+ if (!isset($this->relateduserid)) {
+ throw new \coding_exception('Related user id must be set.');
+ }
+ }
+}
--- /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/>.
+
+/**
+ * User logout event.
+ *
+ * @package core
+ * @copyright 2013 Rajesh Taneja <rajesh@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace core\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Event when user logout.
+ *
+ * @package core
+ * @copyright 2013 Rajesh Taneja <rajesh@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class user_loggedout extends base {
+
+ /**
+ * Initialise required event data properties.
+ */
+ protected function init() {
+ $this->data['objecttable'] = 'user';
+ $this->data['crud'] = 'r';
+ $this->data['level'] = self::LEVEL_OTHER;
+ }
+
+ /**
+ * Returns localised event name.
+ *
+ * @return string
+ */
+ public static function get_name() {
+ return get_string('eventuserloggedout');
+ }
+
+ /**
+ * Returns non-localised event description with id's for admin use only.
+ *
+ * @return string
+ */
+ public function get_description() {
+ return 'User '.$this->objectid.' logged out.';
+ }
+
+ /**
+ * Returns relevant URL.
+ *
+ * @return \moodle_url
+ */
+ public function get_url() {
+ return new \moodle_url('/user/view.php', array('id' => $this->objectid));
+ }
+
+ /**
+ * Return name of the legacy event, which is replaced by this event.
+ *
+ * @return string legacy event name
+ */
+ public static function get_legacy_eventname() {
+ return 'user_logout';
+ }
+
+ /**
+ * Return user_logout legacy event data.
+ *
+ * @return \stdClass user data.
+ */
+ protected function get_legacy_eventdata() {
+ return $this->get_record_snapshot('user', $this->objectid);
+ }
+
+ /**
+ * Returns array of parameters to be passed to legacy add_to_log() function.
+ *
+ * @return array
+ */
+ protected function get_legacy_logdata() {
+ return array(SITEID, 'user', 'logout', 'view.php?id='.$this->objectid.'&course='.SITEID, $this->objectid, 0,
+ $this->objectid);
+ }
+}
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * User updated event.
+ *
+ * @package core
+ * @copyright 2013 Rajesh Taneja <rajesh@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace core\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Event when user profile is updated.
+ *
+ * @package core
+ * @copyright 2013 Rajesh Taneja <rajesh@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class user_updated extends base {
+
+ /**
+ * Initialise required event data properties.
+ */
+ protected function init() {
+ $this->data['objecttable'] = 'user';
+ $this->data['crud'] = 'u';
+ $this->data['level'] = self::LEVEL_OTHER;
+ }
+
+ /**
+ * Returns localised event name.
+ *
+ * @return string
+ */
+ public static function get_name() {
+ return get_string('eventuserupdated');
+ }
+
+ /**
+ * Returns non-localised event description with id's for admin use only.
+ *
+ * @return string
+ */
+ public function get_description() {
+ return 'User profile updated for userid '.$this->objectid;
+ }
+
+ /**
+ * Returns relevant URL.
+ *
+ * @return \moodle_url
+ */
+ public function get_url() {
+ return new \moodle_url('/user/view.php', array('id' => $this->objectid));
+ }
+
+ /**
+ * Return name of the legacy event, which is replaced by this event.
+ *
+ * @return string legacy event name
+ */
+ public static function get_legacy_eventname() {
+ return 'user_updated';
+ }
+
+ /**
+ * Return user_updated legacy event data.
+ *
+ * @return \stdClass user data.
+ */
+ protected function get_legacy_eventdata () {
+ return $this->get_record_snapshot('user', $this->objectid);
+ }
+
+ /**
+ * Returns array of parameters to be passed to legacy add_to_log() function.
+ *
+ * @return array
+ */
+ protected function get_legacy_logdata() {
+ return array(SITEID, 'user', 'update', 'view.php?id='.$this->objectid, '');
+ }
+}
error_reporting($olderror ^ E_NOTICE);
}
+ // No null bytes expected in our data, so let's remove it.
+ $value = str_replace("\0", '', $value);
+
static $buggyiconv = null;
if ($buggyiconv === null) {
$buggyiconv = (!function_exists('iconv') or iconv('UTF-8', 'UTF-8//IGNORE', '100'.chr(130).'€') !== '100€');
'mode' => cache_store::MODE_REQUEST,
'persistent' => true,
),
+ // Used to store external badges.
+ 'externalbadges' => array(
+ 'mode' => cache_store::MODE_APPLICATION,
+ 'simplekeys' => true,
+ 'ttl' => 3600,
+ ),
);
/* List of legacy event handlers */
$handlers = array(
-
- 'user_updated' => array (
- 'handlerfile' => '/lib/badgeslib.php',
- 'handlerfunction' => 'badges_award_handle_profile_criteria_review',
- 'schedule' => 'instant',
- 'internal' => 1,
- ),
-
/*
* portfolio queued event - for non interactive file transfers
* NOTE: this is a HACK, please do not add any more things like this here
array(
'eventname' => '\core\event\course_completed',
'callback' => 'core_badges_observer::course_criteria_review',
+ ),
+ array(
+ 'eventname' => '\core\event\user_updated',
+ 'callback' => 'core_badges_observer::profile_criteria_review',
)
);
'capabilities'=> 'moodle/notes:manage',
),
- // === grade related functions ===
+ // === grading related functions ===
+
+ 'core_grading_get_definitions' => array(
+ 'classname' => 'core_grading_external',
+ 'methodname' => 'get_definitions',
+ 'classpath' => 'grade/externallib.php',
+ 'description' => 'Get grading definitions',
+ 'type' => 'read'
+ ),
'core_grade_get_definitions' => array(
'classname' => 'core_grade_external',
'methodname' => 'get_definitions',
'classpath' => 'grade/externallib.php',
- 'description' => 'Get grading definitions',
+ 'description' => 'DEPRECATED: this deprecated function will be removed in a future version. This function has been renamed as core_grading_get_definitions()',
+ 'type' => 'read'
+ ),
+
+ 'core_grading_get_gradingform_instances' => array(
+ 'classname' => 'core_grading_external',
+ 'methodname' => 'get_gradingform_instances',
+ 'classpath' => 'grade/externallib.php',
+ 'description' => 'Get grading form instances',
'type' => 'read'
),
} else {
$param = str_replace("'", "''", $param);
+ $param = str_replace("\0", "", $param);
$return .= "N'$param'";
}
$return .= $param;
} else {
$param = str_replace("'", "''", $param);
+ $param = str_replace("\0", "", $param);
$return .= "N'$param'";
}
ed.contentDocument.addEventListener('keydown', function() {
ed.contentWindow.focus();
});
+
+ // Whenever a touch event is registered against the content document,
+ // reapply focus. This works around an issue with the location caret not
+ // being focusable without use of the Loupe.
+ ed.contentDocument.addEventListener('touchend', function() {
+ ed.contentWindow.focus();
+ });
});
};
}
if ($ue = $DB->get_record('user_enrolments', array('enrolid'=>$instance->id, 'userid'=>$userid))) {
//only update if timestart or timeend or status are different.
if ($ue->timestart != $timestart or $ue->timeend != $timeend or (!is_null($status) and $ue->status != $status)) {
- $ue->timestart = $timestart;
- $ue->timeend = $timeend;
- if (!is_null($status)) {
- $ue->status = $status;
- }
- $ue->modifierid = $USER->id;
- $ue->timemodified = time();
- $DB->update_record('user_enrolments', $ue);
-
- $updated = true;
+ $this->update_user_enrol($instance, $userid, $status, $timestart, $timeend);
}
} else {
$ue = new stdClass();
}
if ($inserted) {
- // add extra info and trigger event
- $ue->courseid = $courseid;
- $ue->enrol = $name;
- events_trigger('user_enrolled', $ue);
- } else if ($updated) {
- $ue->courseid = $courseid;
- $ue->enrol = $name;
- events_trigger('user_enrol_modified', $ue);
- // resets current enrolment caches
- $context->mark_dirty();
+ // Trigger event.
+ $event = \core\event\user_enrolment_created::create(
+ array(
+ 'objectid' => $ue->id,
+ 'courseid' => $courseid,
+ 'context' => $context,
+ 'relateduserid' => $ue->userid,
+ 'other' => array('enrol' => $name)
+ )
+ );
+ $event->trigger();
}
if ($roleid) {
$DB->update_record('user_enrolments', $ue);
context_course::instance($instance->courseid)->mark_dirty(); // reset enrol caches
- // trigger event
- $ue->courseid = $instance->courseid;
- $ue->enrol = $instance->name;
- events_trigger('user_enrol_modified', $ue);
+ // Trigger event.
+ $event = \core\event\user_enrolment_updated::create(
+ array(
+ 'objectid' => $ue->id,
+ 'courseid' => $instance->courseid,
+ 'context' => context_course::instance($instance->courseid),
+ 'relateduserid' => $ue->userid,
+ 'other' => array('enrol' => $name)
+ )
+ );
+ $event->trigger();
}
/**
WHERE ue.userid = :userid AND e.courseid = :courseid";
if ($DB->record_exists_sql($sql, array('userid'=>$userid, 'courseid'=>$courseid))) {
$ue->lastenrol = false;
- events_trigger('user_unenrolled', $ue);
- // user still has some enrolments, no big cleanup yet
} else {
// the big cleanup IS necessary!
$DB->delete_records('user_lastaccess', array('userid'=>$userid, 'courseid'=>$courseid));
$ue->lastenrol = true; // means user not enrolled any more
- events_trigger('user_unenrolled', $ue);
}
-
+ // Trigger event.
+ $event = \core\event\user_enrolment_deleted::create(
+ array(
+ 'courseid' => $courseid,
+ 'context' => $context,
+ 'relateduserid' => $ue->userid,
+ 'other' => array(
+ 'userenrolment' => (array)$ue,
+ 'enrol' => $name
+ )
+ )
+ );
+ $event->trigger();
// reset all enrol caches
$context->mark_dirty();
}
if ($search_sub) {
- $ldap_result = ldap_search($ldapconnection, $context,
- '(&'.$objectclass.'('.$search_attrib.'='.ldap_filter_addslashes($username).'))',
- array($search_attrib));
+ if (!$ldap_result = @ldap_search($ldapconnection, $context,
+ '(&'.$objectclass.'('.$search_attrib.'='.ldap_filter_addslashes($username).'))',
+ array($search_attrib))) {
+ break; // Not found in this context.
+ }
} else {
$ldap_result = ldap_list($ldapconnection, $context,
'(&'.$objectclass.'('.$search_attrib.'='.ldap_filter_addslashes($username).'))',
}
// You are blocked if you don't have the capability.
- return !has_capability($capability, context_module::instance($this->id));
+ $userid = $this->modinfo->get_user_id();
+ return !has_capability($capability, context_module::instance($this->id), $userid);
}
/**
* @return bool True if the user cannot see the module. False if the activity is either available or should be greyed out.
*/
public function is_user_access_restricted_by_conditional_access() {
- global $CFG, $USER;
+ global $CFG;
if (empty($CFG->enableavailability)) {
return false;
// Shortcut.
return $value;
}
+ // No null bytes expected in our data, so let's remove it.
+ $value = str_replace("\0", '', $value);
// Lower error reporting because glibc throws bogus notices.
$olderror = error_reporting();
function require_logout() {
global $USER;
- $params = $USER;
-
if (isloggedin()) {
- add_to_log(SITEID, "user", "logout", "view.php?id=$USER->id&course=".SITEID, $USER->id, 0, $USER->id);
-
$authsequence = get_enabled_auth_plugins(); // Auths, in sequence.
foreach ($authsequence as $authname) {
$authplugin = get_auth_plugin($authname);
}
}
- events_trigger('user_logout', $params);
+ $event = \core\event\user_loggedout::create(
+ array(
+ 'objectid' => $USER->id,
+ 'context' => context_user::instance($USER->id)
+ )
+ );
+ $event->trigger();
+
session_get_instance()->terminate_current();
- unset($params);
+ unset($GLOBALS['USER']);
}
/**
* @return bool Always returns true
*/
function update_user_login_times() {
- global $USER, $DB;
+ global $USER, $DB, $CFG;
+
+ require_once($CFG->dirroot.'/user/lib.php');
if (isguestuser()) {
// Do not update guest access times/ips for performance.
$USER->lastaccess = $user->lastaccess = $now;
$USER->lastip = $user->lastip = getremoteaddr();
- $DB->update_record('user', $user);
+ user_update_user($user, false);
return true;
}
*/
function create_user_record($username, $password, $auth = 'manual') {
global $CFG, $DB;
- require_once($CFG->dirroot."/user/profile/lib.php");
+ require_once($CFG->dirroot.'/user/profile/lib.php');
+ require_once($CFG->dirroot.'/user/lib.php');
+
// Just in case check text case.
$username = trim(core_text::strtolower($username));
$newuser->timemodified = $newuser->timecreated;
$newuser->mnethostid = $CFG->mnet_localhost_id;
- $newuser->id = $DB->insert_record('user', $newuser);
+ $newuser->id = user_create_user($newuser, false);
// Save user profile data.
profile_save_data($newuser);
// Set the password.
update_internal_user_password($user, $password);
- // Fetch full user record for the event, the complete user data contains too much info
- // and we want to be consistent with other places that trigger this event.
- events_trigger('user_created', $DB->get_record('user', array('id' => $user->id)));
-
return $user;
}
function update_user_record($username) {
global $DB, $CFG;
require_once($CFG->dirroot."/user/profile/lib.php");
+ require_once($CFG->dirroot.'/user/lib.php');
// Just in case check text case.
$username = trim(core_text::strtolower($username));
if ($newuser) {
$newuser['id'] = $oldinfo->id;
$newuser['timemodified'] = time();
- $DB->update_record('user', $newuser);
+ user_update_user((object) $newuser, false);
// Save user profile data.
profile_save_data((object) $newuser);
-
- // Fetch full user record for the event, the complete user data contains too much info
- // and we want to be consistent with other places that trigger this event.
- events_trigger('user_updated', $DB->get_record('user', array('id' => $oldinfo->id)));
}
}
require_once($CFG->libdir.'/gradelib.php');
require_once($CFG->dirroot.'/message/lib.php');
require_once($CFG->dirroot.'/tag/lib.php');
+ require_once($CFG->dirroot.'/user/lib.php');
// Make sure nobody sends bogus record type as parameter.
if (!property_exists($user, 'id') or !property_exists($user, 'username')) {
return false;
}
+ // Keep a copy of user context, we need it for event.
+ $usercontext = context_user::instance($user->id);
+
// Delete all grades - backup is kept in grade_grades_history table.
grade_user_delete($user->id);
// Force logout - may fail if file based sessions used, sorry.
session_kill_user($user->id);
- // Now do a final accesslib cleanup - removes all role assignments in user context and context itself.
- context_helper::delete_instance(CONTEXT_USER, $user->id);
-
// Workaround for bulk deletes of users with the same email address.
$delname = "$user->email.".time();
while ($DB->record_exists('user', array('username' => $delname))) { // No need to use mnethostid here.
$updateuser->picture = 0;
$updateuser->timemodified = time();
- $DB->update_record('user', $updateuser);
- // Add this action to log.
- add_to_log(SITEID, 'user', 'delete', "view.php?id=$user->id", $user->firstname.' '.$user->lastname);
+ user_update_user($updateuser, false);
+
+ // Now do a final accesslib cleanup - removes all role assignments in user context and context itself.
+ context_helper::delete_instance(CONTEXT_USER, $user->id);
+
+ // Any plugin that needs to cleanup should register this event.
+ // Trigger event.
+ $event = \core\event\user_deleted::create(
+ array(
+ 'objectid' => $user->id,
+ 'context' => $usercontext,
+ 'other' => array('user' => (array)clone $user)
+ )
+ );
+ $event->add_record_snapshot('user', $updateuser);
+ $event->trigger();
// We will update the user's timemodified, as it will be passed to the user_deleted event, which
// should know about this updated property persisted to the user's table.
$authplugin = get_auth_plugin($user->auth);
$authplugin->user_delete($user);
- // Any plugin that needs to cleanup should register this event.
- events_trigger('user_deleted', $user);
-
return true;
}
*/
public $attributessecondary = array();
+ /**
+ * The string to use next to the icon for the action icon relating to the secondary (dropdown) menu.
+ * @var array
+ */
+ public $actiontext = null;
+
+ /**
+ * An icon to use for the toggling the secondary menu (dropdown).
+ * @var pix_icon
+ */
+ public $actionicon;
+
/**
* Constructs the action menu with the given items.
*
'aria-labelledby' => 'action-menu-toggle-'.$this->instance,
'role' => 'menu'
);
+ $this->actionicon = new pix_icon(
+ 't/contextmenu',
+ new lang_string('actions', 'moodle'),
+ 'moodle',
+ array('class' => 'iconsmall', 'title' => '')
+ );
$this->set_alignment(self::TR, self::BR);
foreach ($actions as $action) {
$this->add($action);
if ($output === null) {
$output = $OUTPUT;
}
- $title = get_string('actions', 'moodle');
- $pixicon = $output->pix_icon(
- 't/contextmenu',
- $title,
- 'moodle',
- array('class' => 'iconsmall', 'title' => '')
- );
-
+ $pixicon = $this->actionicon;
+ $title = new lang_string('actions', 'moodle');
+ if ($pixicon instanceof renderable) {
+ $pixicon = $output->render($pixicon);
+ if ($pixicon instanceof pix_icon && isset($pixicon->attributes['alt'])) {
+ $title = $pixicon->attributes['alt'];
+ }
+ }
+ $string = '';
+ if ($this->actiontext) {
+ $string = $this->actiontext;
+ }
$actions = $this->primaryactions;
$attributes = array(
'class' => 'toggle-display',
'id' => 'action-menu-toggle-'.$this->instance,
'role' => 'menuitem'
);
- $actions[] = html_writer::link('#', $pixicon, $attributes);
+ $actions[] = html_writer::link('#', $string.$pixicon, $attributes);
return $actions;
}
* @param bool $primary Whether this is a primary action or not.
* @param array $attributes Any attribtues associated with the action.
*/
- public function __construct(moodle_url $url, pix_icon $icon, $text, $primary = true, array $attributes = array()) {
+ public function __construct(moodle_url $url, pix_icon $icon = null, $text, $primary = true, array $attributes = array()) {
parent::__construct($url, $text, null, $attributes, $icon);
$this->primary = (bool)$primary;
$this->add_class('menu-action');