diff --git a/admin/cli/automated_backups.php b/admin/cli/automated_backups.php
index dc80c7aedc1..3c3841e718d 100644
--- a/admin/cli/automated_backups.php
+++ b/admin/cli/automated_backups.php
@@ -75,8 +75,7 @@ if (!empty($CFG->showcronsql)) {
$DB->set_debug(true);
}
if (!empty($CFG->showcrondebugging)) {
- $CFG->debug = DEBUG_DEVELOPER;
- $CFG->debugdisplay = true;
+ set_debugging(DEBUG_DEVELOPER, true);
}
$starttime = microtime();
diff --git a/admin/cli/install.php b/admin/cli/install.php
index 21617cf0860..fcd25b248fa 100644
--- a/admin/cli/install.php
+++ b/admin/cli/install.php
@@ -162,6 +162,9 @@ $CFG->running_installer = true;
$CFG->early_install_lang = true;
$CFG->ostype = (stristr(PHP_OS, 'win') && !stristr(PHP_OS, 'darwin')) ? 'WINDOWS' : 'UNIX';
$CFG->dboptions = array();
+$CFG->debug = (E_ALL | E_STRICT);
+$CFG->debugdisplay = true;
+$CFG->debugdeveloper = true;
$parts = explode('/', str_replace('\\', '/', dirname(dirname(__FILE__))));
$CFG->admin = array_pop($parts);
diff --git a/admin/qbehaviours.php b/admin/qbehaviours.php
index cb59976e6b6..85f0c8ef905 100644
--- a/admin/qbehaviours.php
+++ b/admin/qbehaviours.php
@@ -143,7 +143,7 @@ if (($delete = optional_param('delete', '', PARAM_PLUGIN)) && confirm_sesskey())
print_error('cannotdeletemissingbehaviour', 'question', $thispageurl);
}
- if (!isset($behaviours[$delete])) {
+ if (!isset($behaviours[$delete]) && !get_config('qbehaviour_' . $delete, 'version')) {
print_error('unknownbehaviour', 'question', $thispageurl, $delete);
}
@@ -171,10 +171,7 @@ if (($delete = optional_param('delete', '', PARAM_PLUGIN)) && confirm_sesskey())
echo $OUTPUT->header();
echo $OUTPUT->heading(get_string('deletingbehaviour', 'question', $behaviourname));
- // Delete any configuration records.
- if (!unset_all_config_for_plugin('qbehaviour_' . $delete)) {
- echo $OUTPUT->notification(get_string('errordeletingconfig', 'admin', 'qbehaviour_' . $delete));
- }
+ // Remove this behaviour from configurations where it might appear.
if (($key = array_search($delete, $disabledbehaviours)) !== false) {
unset($disabledbehaviours[$key]);
set_config('disabledbehaviours', implode(',', $disabledbehaviours), 'question');
@@ -185,12 +182,10 @@ if (($delete = optional_param('delete', '', PARAM_PLUGIN)) && confirm_sesskey())
set_config('behavioursortorder', implode(',', $behaviourorder), 'question');
}
- // Then the tables themselves
- drop_plugin_tables($delete, core_component::get_plugin_directory('qbehaviour', $delete) . '/db/install.xml', false);
-
- // Remove event handlers and dequeue pending events
- events_uninstall('qbehaviour_' . $delete);
+ // Then uninstall the plugin.
+ uninstall_plugin('qbehaviour', $delete);
+ // Display a message.
$a = new stdClass();
$a->behaviour = $behaviourname;
$a->directory = core_component::get_plugin_directory('qbehaviour', $delete);
diff --git a/admin/qtypes.php b/admin/qtypes.php
index e12d3cc4fa3..3e865d14160 100644
--- a/admin/qtypes.php
+++ b/admin/qtypes.php
@@ -129,7 +129,7 @@ if (($delete = optional_param('delete', '', PARAM_PLUGIN)) && confirm_sesskey())
print_error('cannotdeletemissingqtype', 'question', $thispageurl);
}
- if (!isset($qtypes[$delete])) {
+ if (!isset($qtypes[$delete]) && !get_config('qtype_' . $delete, 'version')) {
print_error('unknownquestiontype', 'question', $thispageurl, $delete);
}
@@ -158,18 +158,12 @@ if (($delete = optional_param('delete', '', PARAM_PLUGIN)) && confirm_sesskey())
echo $OUTPUT->header();
echo $OUTPUT->heading(get_string('deletingqtype', 'question', $qtypename));
- // Delete any configuration records.
- if (!unset_all_config_for_plugin('qtype_' . $delete)) {
- echo $OUTPUT->notification(get_string('errordeletingconfig', 'admin', 'qtype_' . $delete));
- }
+ // Delete any questoin configuration records mentioning this plugin.
unset_config($delete . '_disabled', 'question');
unset_config($delete . '_sortorder', 'question');
- // Then the tables themselves
- drop_plugin_tables($delete, $qtypes[$delete]->plugin_dir() . '/db/install.xml', false);
-
- // Remove event handlers and dequeue pending events
- events_uninstall('qtype_' . $delete);
+ // Then uninstall the plugin.
+ uninstall_plugin('qtype', $delete);
$a = new stdClass();
$a->qtype = $qtypename;
diff --git a/admin/tool/generator/classes/backend.php b/admin/tool/generator/classes/backend.php
new file mode 100644
index 00000000000..beed42112e1
--- /dev/null
+++ b/admin/tool/generator/classes/backend.php
@@ -0,0 +1,606 @@
+.
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Backend code for the 'make large course' tool.
+ *
+ * @package tool_generator
+ * @copyright 2013 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class tool_generator_backend {
+ /**
+ * @var int Lowest (smallest) size index
+ */
+ const MIN_SIZE = 0;
+ /**
+ * @var int Highest (largest) size index
+ */
+ const MAX_SIZE = 5;
+ /**
+ * @var int Default size index
+ */
+ const DEFAULT_SIZE = 3;
+
+ /**
+ * @var array Number of sections in course
+ */
+ private static $paramsections = array(1, 10, 100, 500, 1000, 2000);
+ /**
+ * @var array Number of Page activities in course
+ */
+ private static $parampages = array(1, 50, 200, 1000, 5000, 10000);
+ /**
+ * @var array Number of students enrolled in course
+ */
+ private static $paramusers = array(1, 100, 1000, 10000, 50000, 100000);
+ /**
+ * Total size of small files: 1KB, 1MB, 10MB, 100MB, 1GB, 2GB.
+ *
+ * @var array Number of small files created in a single file activity
+ */
+ private static $paramsmallfilecount = array(1, 64, 128, 1024, 16384, 32768);
+ /**
+ * @var array Size of small files (to make the totals into nice numbers)
+ */
+ private static $paramsmallfilesize = array(1024, 16384, 81920, 102400, 65536, 65536);
+ /**
+ * Total size of big files: 8KB, 8MB, 80MB, 800MB, 8GB, 16GB.
+ *
+ * @var array Number of big files created as individual file activities
+ */
+ private static $parambigfilecount = array(1, 2, 5, 10, 10, 10);
+ /**
+ * @var array Size of each large file
+ */
+ private static $parambigfilesize = array(8192, 4194304, 16777216, 83886080,
+ 858993459, 1717986918);
+ /**
+ * @var array Number of forum discussions
+ */
+ private static $paramforumdiscussions = array(1, 10, 100, 500, 1000, 2000);
+ /**
+ * @var array Number of forum posts per discussion
+ */
+ private static $paramforumposts = array(2, 2, 5, 10, 10, 10);
+
+ /**
+ * @var string Course shortname
+ */
+ private $shortname;
+
+ /**
+ * @var int Size code (index in the above arrays)
+ */
+ private $size;
+
+ /**
+ * @var bool True if displaying progress
+ */
+ private $progress;
+
+ /**
+ * @var testing_data_generator Data generator
+ */
+ private $generator;
+
+ /**
+ * @var stdClass Course object
+ */
+ private $course;
+
+ /**
+ * @var int Epoch time at which last dot was displayed
+ */
+ private $lastdot;
+
+ /**
+ * @var int Epoch time at which last percentage was displayed
+ */
+ private $lastpercentage;
+
+ /**
+ * @var int Epoch time at which current step (current set of dots) started
+ */
+ private $starttime;
+
+ /**
+ * @var array Array from test user number (1...N) to userid in database
+ */
+ private $userids;
+
+ /**
+ * Constructs object ready to create course.
+ *
+ * @param string $shortname Course shortname
+ * @param int $size Size as numeric index
+ * @param bool $progress True if progress information should be displayed
+ * @return int Course id
+ * @throws coding_exception If parameters are invalid
+ */
+ public function __construct($shortname, $size, $progress = true) {
+ // Check parameter.
+ if ($size < self::MIN_SIZE || $size > self::MAX_SIZE) {
+ throw new coding_exception('Invalid size');
+ }
+
+ // Set parameters.
+ $this->shortname = $shortname;
+ $this->size = $size;
+ $this->progress = $progress;
+ }
+
+ /**
+ * Gets a list of size choices supported by this backend.
+ *
+ * @return array List of size (int) => text description for display
+ */
+ public static function get_size_choices() {
+ $options = array();
+ for ($size = self::MIN_SIZE; $size <= self::MAX_SIZE; $size++) {
+ $options[$size] = get_string('size_' . $size, 'tool_generator');
+ }
+ return $options;
+ }
+
+ /**
+ * Converts a size name into the numeric constant.
+ *
+ * @param string $sizename Size name e.g. 'L'
+ * @return int Numeric version
+ * @throws coding_exception If the size name is not known
+ */
+ public static function size_for_name($sizename) {
+ for ($size = self::MIN_SIZE; $size <= self::MAX_SIZE; $size++) {
+ if ($sizename == get_string('shortsize_' . $size, 'tool_generator')) {
+ return $size;
+ }
+ }
+ throw new coding_exception("Unknown size name '$sizename'");
+ }
+
+ /**
+ * Checks that a shortname is available (unused).
+ *
+ * @param string $shortname Proposed course shortname
+ * @return string An error message if the name is unavailable or '' if OK
+ */
+ public static function check_shortname_available($shortname) {
+ global $DB;
+ $fullname = $DB->get_field('course', 'fullname',
+ array('shortname' => $shortname), IGNORE_MISSING);
+ if ($fullname !== false) {
+ // I wanted to throw an exception here but it is not possible to
+ // use strings from moodle.php in exceptions, and I didn't want
+ // to duplicate the string in tool_generator, so I changed this to
+ // not use exceptions.
+ return get_string('shortnametaken', 'moodle', $fullname);
+ }
+ return '';
+ }
+
+ /**
+ * Runs the entire 'make' process.
+ *
+ * @return int Course id
+ */
+ public function make() {
+ global $DB, $CFG;
+ require_once($CFG->dirroot . '/lib/phpunit/classes/util.php');
+
+ raise_memory_limit(MEMORY_EXTRA);
+
+ if ($this->progress && !CLI_SCRIPT) {
+ echo html_writer::start_tag('ul');
+ }
+
+ $entirestart = microtime(true);
+
+ // Start transaction.
+ $transaction = $DB->start_delegated_transaction();
+
+ // Get generator.
+ $this->generator = phpunit_util::get_data_generator();
+
+ // Make course.
+ $this->course = $this->create_course();
+ $this->create_users();
+ $this->create_pages();
+ $this->create_small_files();
+ $this->create_big_files();
+ $this->create_forum();
+
+ // Log total time.
+ $this->log('complete', round(microtime(true) - $entirestart, 1));
+
+ if ($this->progress && !CLI_SCRIPT) {
+ echo html_writer::end_tag('ul');
+ }
+
+ // Commit transaction and finish.
+ $transaction->allow_commit();
+ return $this->course->id;
+ }
+
+ /**
+ * Creates the actual course.
+ *
+ * @return stdClass Course record
+ */
+ private function create_course() {
+ $this->log('createcourse', $this->shortname);
+ $courserecord = array('shortname' => $this->shortname,
+ 'fullname' => get_string('fullname', 'tool_generator',
+ array('size' => get_string('shortsize_' . $this->size, 'tool_generator'))),
+ 'numsections' => self::$paramsections[$this->size]);
+ return $this->generator->create_course($courserecord, array('createsections' => true));
+ }
+
+ /**
+ * Creates a number of user accounts and enrols them on the course.
+ * Note: Existing user accounts that were created by this system are
+ * reused if available.
+ */
+ private function create_users() {
+ global $DB;
+
+ // Work out total number of users.
+ $count = self::$paramusers[$this->size];
+
+ // Get existing users in order. We will 'fill up holes' in this up to
+ // the required number.
+ $this->log('checkaccounts', $count);
+ $nextnumber = 1;
+ $rs = $DB->get_recordset_select('user', $DB->sql_like('username', '?'),
+ array('tool_generator_%'), 'username', 'id, username');
+ foreach ($rs as $rec) {
+ // Extract number from username.
+ $matches = array();
+ if (!preg_match('~^tool_generator_([0-9]{6})$~', $rec->username, $matches)) {
+ continue;
+ }
+ $number = (int)$matches[1];
+
+ // Create missing users in range up to this.
+ if ($number != $nextnumber) {
+ $this->create_user_accounts($nextnumber, min($number - 1, $count));
+ } else {
+ $this->userids[$number] = (int)$rec->id;
+ }
+
+ // Stop if we've got enough users.
+ $nextnumber = $number + 1;
+ if ($number >= $count) {
+ break;
+ }
+ }
+ $rs->close();
+
+ // Create users from end of existing range.
+ if ($nextnumber <= $count) {
+ $this->create_user_accounts($nextnumber, $count);
+ }
+
+ // Assign all users to course.
+ $this->log('enrol', $count, true);
+
+ $enrolplugin = enrol_get_plugin('manual');
+ $instances = enrol_get_instances($this->course->id, true);
+ foreach ($instances as $instance) {
+ if ($instance->enrol === 'manual') {
+ break;
+ }
+ }
+ if ($instance->enrol !== 'manual') {
+ throw new coding_exception('No manual enrol plugin in course');
+ }
+ $role = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);
+
+ for ($number = 1; $number <= $count; $number++) {
+ // Enrol user.
+ $enrolplugin->enrol_user($instance, $this->userids[$number], $role->id);
+ $this->dot($number, $count);
+ }
+
+ $this->end_log();
+ }
+
+ /**
+ * Creates user accounts with a numeric range.
+ *
+ * @param int $first Number of first user
+ * @param int $last Number of last user
+ */
+ private function create_user_accounts($first, $last) {
+ $this->log('createaccounts', (object)array('from' => $first, 'to' => $last), true);
+ $count = $last - $first + 1;
+ $done = 0;
+ for ($number = $first; $number <= $last; $number++, $done++) {
+ // Work out username with 6-digit number.
+ $textnumber = (string)$number;
+ while (strlen($textnumber) < 6) {
+ $textnumber = '0' . $textnumber;
+ }
+ $username = 'tool_generator_' . $textnumber;
+
+ // Create user account.
+ $record = array('firstname' => get_string('firstname', 'tool_generator'),
+ 'lastname' => $number, 'username' => $username);
+ $user = $this->generator->create_user($record);
+ $this->userids[$number] = (int)$user->id;
+ $this->dot($done, $count);
+ }
+ $this->end_log();
+ }
+
+ /**
+ * Creates a number of Page activities.
+ */
+ private function create_pages() {
+ // Set up generator.
+ $pagegenerator = $this->generator->get_plugin_generator('mod_page');
+
+ // Create pages.
+ $number = self::$parampages[$this->size];
+ $this->log('createpages', $number, true);
+ for ($i=0; $i<$number; $i++) {
+ $record = array('course' => $this->course->id);
+ $options = array('section' => $this->get_random_section());
+ $pagegenerator->create_instance($record, $options);
+ $this->dot($i, $number);
+ }
+
+ $this->end_log();
+ }
+
+ /**
+ * Creates one resource activity with a lot of small files.
+ */
+ private function create_small_files() {
+ $count = self::$paramsmallfilecount[$this->size];
+ $this->log('createsmallfiles', $count, true);
+
+ // Create resource with default textfile only.
+ $resourcegenerator = $this->generator->get_plugin_generator('mod_resource');
+ $record = array('course' => $this->course->id,
+ 'name' => get_string('smallfiles', 'tool_generator'));
+ $options = array('section' => 0);
+ $resource = $resourcegenerator->create_instance($record, $options);
+
+ // Add files.
+ $fs = get_file_storage();
+ $context = context_module::instance($resource->cmid);
+ $filerecord = array('component' => 'mod_resource', 'filearea' => 'content',
+ 'contextid' => $context->id, 'itemid' => 0, 'filepath' => '/');
+ for ($i = 0; $i < $count; $i++) {
+ $filerecord['filename'] = 'smallfile' . $i . '.dat';
+
+ // Generate random binary data (different for each file so it
+ // doesn't compress unrealistically).
+ $data = self::get_random_binary(self::$paramsmallfilesize[$this->size]);
+
+ $fs->create_file_from_string($filerecord, $data);
+ $this->dot($i, $count);
+ }
+
+ $this->end_log();
+ }
+
+ /**
+ * Creates a string of random binary data. The start of the string includes
+ * the current time, in an attempt to avoid large-scale repetition.
+ *
+ * @param int $length Number of bytes
+ * @return Random data
+ */
+ private static function get_random_binary($length) {
+ $data = microtime(true);
+ if (strlen($data) > $length) {
+ // Use last digits of data.
+ return substr($data, -$length);
+ }
+ $length -= strlen($data);
+ for ($j=0; $j < $length; $j++) {
+ $data .= chr(rand(1, 255));
+ }
+ return $data;
+ }
+
+ /**
+ * Creates a number of resource activities with one big file each.
+ */
+ private function create_big_files() {
+ global $CFG;
+
+ // Work out how many files and how many blocks to use (up to 64KB).
+ $count = self::$parambigfilecount[$this->size];
+ $blocks = ceil(self::$parambigfilesize[$this->size] / 65536);
+ $blocksize = floor(self::$parambigfilesize[$this->size] / $blocks);
+
+ $this->log('createbigfiles', $count, true);
+
+ // Prepare temp area.
+ $tempfolder = make_temp_directory('tool_generator');
+ $tempfile = $tempfolder . '/' . rand();
+
+ // Create resources and files.
+ $fs = get_file_storage();
+ $resourcegenerator = $this->generator->get_plugin_generator('mod_resource');
+ for ($i = 0; $i < $count; $i++) {
+ // Create resource.
+ $record = array('course' => $this->course->id,
+ 'name' => get_string('bigfile', 'tool_generator', $i));
+ $options = array('section' => $this->get_random_section());
+ $resource = $resourcegenerator->create_instance($record, $options);
+
+ // Write file.
+ $handle = fopen($tempfile, 'w');
+ if (!$handle) {
+ throw new coding_exception('Failed to open temporary file');
+ }
+ for ($j = 0; $j < $blocks; $j++) {
+ $data = self::get_random_binary($blocksize);
+ fwrite($handle, $data);
+ $this->dot($i * $blocks + $j, $count * $blocks);
+ }
+ fclose($handle);
+
+ // Add file.
+ $context = context_module::instance($resource->cmid);
+ $filerecord = array('component' => 'mod_resource', 'filearea' => 'content',
+ 'contextid' => $context->id, 'itemid' => 0, 'filepath' => '/',
+ 'filename' => 'bigfile' . $i . '.dat');
+ $fs->create_file_from_pathname($filerecord, $tempfile);
+ }
+
+ unlink($tempfile);
+ $this->end_log();
+ }
+
+ /**
+ * Creates one forum activity with a bunch of posts.
+ */
+ private function create_forum() {
+ global $DB;
+
+ $discussions = self::$paramforumdiscussions[$this->size];
+ $posts = self::$paramforumposts[$this->size];
+ $totalposts = $discussions * $posts;
+
+ $this->log('createforum', $totalposts, true);
+
+ // Create empty forum.
+ $forumgenerator = $this->generator->get_plugin_generator('mod_forum');
+ $record = array('course' => $this->course->id,
+ 'name' => get_string('pluginname', 'forum'));
+ $options = array('section' => 0);
+ $forum = $forumgenerator->create_instance($record, $options);
+
+ // Add discussions and posts.
+ $sofar = 0;
+ for ($i=0; $i < $discussions; $i++) {
+ $record = array('forum' => $forum->id, 'course' => $this->course->id,
+ 'userid' => $this->get_random_user());
+ $discussion = $forumgenerator->create_discussion($record);
+ $parentid = $DB->get_field('forum_posts', 'id', array('discussion' => $discussion->id), MUST_EXIST);
+ $sofar++;
+ for ($j=0; $j < $posts - 1; $j++, $sofar++) {
+ $record = array('discussion' => $discussion->id,
+ 'userid' => $this->get_random_user(), 'parent' => $parentid);
+ $forumgenerator->create_post($record);
+ $this->dot($sofar, $totalposts);
+ }
+ }
+
+ $this->end_log();
+ }
+
+ /**
+ * Gets a random section number.
+ *
+ * @return int A section number from 1 to the number of sections
+ */
+ private function get_random_section() {
+ return rand(1, self::$paramsections[$this->size]);
+ }
+
+ /**
+ * Gets a random user id.
+ *
+ * @return int A user id for a random created user
+ */
+ private function get_random_user() {
+ return $this->userids[rand(1, self::$paramusers[$this->size])];
+ }
+
+ /**
+ * Displays information as part of progress.
+ * @param string $langstring Part of langstring (after progress_)
+ * @param mixed $a Optional lang string parameters
+ * @param bool $leaveopen If true, doesn't close LI tag (ready for dots)
+ */
+ private function log($langstring, $a = null, $leaveopen = false) {
+ if (!$this->progress) {
+ return;
+ }
+ if (CLI_SCRIPT) {
+ echo '* ';
+ } else {
+ echo html_writer::start_tag('li');
+ }
+ echo get_string('progress_' . $langstring, 'tool_generator', $a);
+ if (!$leaveopen) {
+ if (CLI_SCRIPT) {
+ echo "\n";
+ } else {
+ echo html_writer::end_tag('li');
+ }
+ } else {
+ echo ': ';
+ $this->lastdot = time();
+ $this->lastpercentage = $this->lastdot;
+ $this->starttime = microtime(true);
+ }
+ }
+
+ /**
+ * Outputs dots. There is up to one dot per second. Once a minute, it
+ * displays a percentage.
+ * @param int $number Number of completed items
+ * @param int $total Total number of items to complete
+ */
+ private function dot($number, $total) {
+ if (!$this->progress) {
+ return;
+ }
+ $now = time();
+ if ($now == $this->lastdot) {
+ return;
+ }
+ $this->lastdot = $now;
+ if (CLI_SCRIPT) {
+ echo '.';
+ } else {
+ echo ' . ';
+ }
+ if ($now - $this->lastpercentage >= 30) {
+ echo round(100.0 * $number / $total, 1) . '%';
+ $this->lastpercentage = $now;
+ }
+
+ // Update time limit so PHP doesn't time out.
+ if (!CLI_SCRIPT) {
+ set_time_limit(120);
+ }
+ }
+
+ /**
+ * Ends a log string that was started using log function with $leaveopen.
+ */
+ private function end_log() {
+ if (!$this->progress) {
+ return;
+ }
+ echo get_string('done', 'tool_generator', round(microtime(true) - $this->starttime, 1));
+ if (CLI_SCRIPT) {
+ echo "\n";
+ } else {
+ echo html_writer::end_tag('li');
+ }
+ }
+}
diff --git a/admin/tool/generator/classes/make_form.php b/admin/tool/generator/classes/make_form.php
new file mode 100644
index 00000000000..25c8350fb73
--- /dev/null
+++ b/admin/tool/generator/classes/make_form.php
@@ -0,0 +1,59 @@
+.
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->libdir . '/formslib.php');
+
+/**
+ * Form with options for creating large course.
+ *
+ * @package tool_generator
+ * @copyright 2013 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class tool_generator_make_form extends moodleform {
+
+ public function definition() {
+ $mform = $this->_form;
+
+ $mform->addElement('select', 'size', get_string('size', 'tool_generator'),
+ tool_generator_backend::get_size_choices());
+ $mform->setDefault('size', tool_generator_backend::DEFAULT_SIZE);
+
+ $mform->addElement('text', 'shortname', get_string('shortnamecourse'));
+ $mform->addRule('shortname', get_string('missingshortname'), 'required', null, 'client');
+ $mform->setType('shortname', PARAM_TEXT);
+
+ $mform->addElement('submit', 'submit', get_string('createcourse', 'tool_generator'));
+ }
+
+ public function validation($data, $files) {
+ global $DB;
+ $errors = array();
+
+ // Check course doesn't already exist.
+ if (!empty($data['shortname'])) {
+ // Check shortname.
+ $error = tool_generator_backend::check_shortname_available($data['shortname']);
+ if ($error) {
+ $errors['shortname'] = $error;
+ }
+ }
+
+ return $errors;
+ }
+}
diff --git a/admin/tool/generator/cli/generate.php b/admin/tool/generator/cli/generate.php
index 4514fd7cea8..353883e1d30 100644
--- a/admin/tool/generator/cli/generate.php
+++ b/admin/tool/generator/cli/generate.php
@@ -28,7 +28,7 @@ define('CLI_SCRIPT', true);
require(dirname(__FILE__) . '/../../../../config.php');
require_once(dirname(__FILE__) . '/../locallib.php');
-if (!debugging('', DEBUG_DEVELOPER)) {
+if (!$CFG->debugdeveloper) {
echo("This script is for developers only!!!\n");
exit(1);
}
diff --git a/admin/tool/generator/cli/maketestcourse.php b/admin/tool/generator/cli/maketestcourse.php
new file mode 100644
index 00000000000..b646e2f94af
--- /dev/null
+++ b/admin/tool/generator/cli/maketestcourse.php
@@ -0,0 +1,94 @@
+.
+
+/**
+ * CLI interface for creating a test course.
+ *
+ * @package tool_generator
+ * @copyright 2013 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+define('CLI_SCRIPT', true);
+define('NO_OUTPUT_BUFFERING', true);
+
+require(dirname(__FILE__) . '/../../../../config.php');
+require_once($CFG->libdir. '/clilib.php');
+
+// CLI options.
+list($options, $unrecognized) = cli_get_params(
+ array(
+ 'help' => false,
+ 'shortname' => false,
+ 'size' => false,
+ 'bypasscheck' => false,
+ 'quiet' => false
+ ),
+ array(
+ 'h' => 'help'
+ )
+);
+
+// Display help.
+if (!empty($options['help']) || empty($options['shortname']) || empty($options['size'])) {
+ echo "
+Utility to create standard test course. (Also available in GUI interface.)
+
+Not for use on live sites; only normally works if debugging is set to DEVELOPER
+level.
+
+Options:
+--shortname Shortname of course to create (required)
+--size Size of course to create XS, S, M, L, XL, or XXL (required)
+--bypasscheck Bypasses the developer-mode check (be careful!)
+--quiet Do not show any output
+
+-h, --help Print out this help
+
+Example from Moodle root directory:
+\$ php admin/tool/generator/cli/maketestcourse.php --shortname=SIZE_S --size=S
+";
+ // Exit with error unless we're showing this because they asked for it.
+ exit(empty($options['help']) ? 1 : 0);
+}
+
+// Check debugging is set to developer level.
+if (empty($options['bypasscheck']) && !debugging('', DEBUG_DEVELOPER)) {
+ cli_error(get_string('error_notdebugging', 'tool_generator'));
+}
+
+// Get options.
+$shortname = $options['shortname'];
+$sizename = $options['size'];
+
+// Check size.
+try {
+ $size = tool_generator_backend::size_for_name($sizename);
+} catch (coding_exception $e) {
+ cli_error("Invalid size ($sizename). Use --help for help.");
+}
+
+// Check shortname.
+if ($error = tool_generator_backend::check_shortname_available($shortname)) {
+ cli_error($error);
+}
+
+// Switch to admin user account.
+session_set_user(get_admin());
+
+// Do backend code to generate course.
+$backend = new tool_generator_backend($shortname, $size, empty($options['quiet']));
+$id = $backend->make();
diff --git a/admin/tool/generator/index.php b/admin/tool/generator/index.php
index f77c8e0e1af..7a917e91bae 100644
--- a/admin/tool/generator/index.php
+++ b/admin/tool/generator/index.php
@@ -34,7 +34,7 @@ if (!is_siteadmin()) {
error('Only for admins');
}
-if (!debugging('', DEBUG_DEVELOPER)) {
+if (!$CFG->debugdeveloper) {
error('This script is for developers only!!!');
}
diff --git a/admin/tool/generator/lang/en/tool_generator.php b/admin/tool/generator/lang/en/tool_generator.php
index 051d04f5c1c..103a4fdfd22 100644
--- a/admin/tool/generator/lang/en/tool_generator.php
+++ b/admin/tool/generator/lang/en/tool_generator.php
@@ -15,12 +15,60 @@
// along with Moodle. If not, see .
/**
- * Strings for component 'tool_generator', language 'en', branch 'MOODLE_22_STABLE'
+ * Language strings.
*
- * @package tool
- * @subpackage generator
- * @copyright 2011 Petr Skoda
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @package tool_generator
+ * @copyright 2013 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
+$string['bigfile'] = 'Big file {$a}';
+$string['createcourse'] = 'Create course';
+$string['creating'] = 'Creating course';
+$string['done'] = 'done ({$a}s)';
+$string['explanation'] = 'This tool creates standard test courses that include many
+sections, activities, and files.
+
+This is intended to provide a standardised measure for checking the reliability
+and performance of various system components (such as backup and restore).
+
+This test is important because there have been many cases previously where,
+faced with real-life use cases (e.g. a course with 1,000 activities), the system
+does not work.
+
+Courses created using this feature can occupy a large amount of database and
+filesystem space (tens of gigabytes). You will need to delete the courses
+(and wait for various cleanup runs) to release this space again.
+
+**Do not use this feature on a live system**. Use only on a developer server.
+(To avoid accidental use, this feature is disabled unless you have also selected
+DEVELOPER debugging level.)';
+
+$string['error_notdebugging'] = 'Not available on this server because debugging is not set to DEVELOPER';
+$string['firstname'] = 'Test course user';
+$string['fullname'] = 'Test course: {$a->size}';
+$string['maketestcourse'] = 'Make test course';
$string['pluginname'] = 'Random course generator';
+$string['progress_createcourse'] = 'Creating course {$a}';
+$string['progress_checkaccounts'] = 'Checking user accounts ({$a})';
+$string['progress_createaccounts'] = 'Creating user accounts ({$a->from} - {$a->to})';
+$string['progress_createbigfiles'] = 'Creating big files ({$a})';
+$string['progress_createforum'] = 'Creating forum ({$a} posts)';
+$string['progress_createpages'] = 'Creating pages ({$a})';
+$string['progress_createsmallfiles'] = 'Creating small files ({$a})';
+$string['progress_enrol'] = 'Enrolling users into course ({$a})';
+$string['progress_complete'] = 'Complete ({$a}s)';
+$string['shortsize_0'] = 'XS';
+$string['shortsize_1'] = 'S';
+$string['shortsize_2'] = 'M';
+$string['shortsize_3'] = 'L';
+$string['shortsize_4'] = 'XL';
+$string['shortsize_5'] = 'XXL';
+$string['size'] = 'Size of course';
+$string['size_0'] = 'XS (~10KB; create in ~1 second)';
+$string['size_1'] = 'S (~10MB; create in ~30 seconds)';
+$string['size_2'] = 'M (~100MB; create in ~5 minutes)';
+$string['size_3'] = 'L (~1GB; create in ~1 hour)';
+$string['size_4'] = 'XL (~10GB; create in ~4 hours)';
+$string['size_5'] = 'XXL (~20GB; create in ~8 hours)';
+$string['smallfiles'] = 'Small files';
diff --git a/admin/tool/generator/maketestcourse.php b/admin/tool/generator/maketestcourse.php
new file mode 100644
index 00000000000..f3ff9f77195
--- /dev/null
+++ b/admin/tool/generator/maketestcourse.php
@@ -0,0 +1,70 @@
+.
+
+/**
+ * Script creates a standardised large course for testing reliability and
+ * performance.
+ *
+ * @package tool_generator
+ * @copyright 2013 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+// Disable buffering so that the progress output displays gradually without
+// needing to call flush().
+define('NO_OUTPUT_BUFFERING', true);
+
+require('../../../config.php');
+
+require_once($CFG->libdir . '/adminlib.php');
+
+// Initialise page and check permissions.
+admin_externalpage_setup('toolgenerator');
+
+// Start page.
+echo $OUTPUT->header();
+echo $OUTPUT->heading(get_string('maketestcourse', 'tool_generator'));
+
+// Information message.
+$context = context_system::instance();
+echo $OUTPUT->box(format_text(get_string('explanation', 'tool_generator'),
+ FORMAT_MARKDOWN, array('context' => $context)));
+
+// Check debugging is set to DEVELOPER.
+if (!debugging('', DEBUG_DEVELOPER)) {
+ echo $OUTPUT->notification(get_string('error_notdebugging', 'tool_generator'));
+ echo $OUTPUT->footer();
+ exit;
+}
+
+// Set up the form.
+$mform = new tool_generator_make_form('maketestcourse.php');
+if ($data = $mform->get_data()) {
+ // Do actual work.
+ echo $OUTPUT->heading(get_string('creating', 'tool_generator'));
+ $backend = new tool_generator_backend($data->shortname, $data->size);
+ $id = $backend->make();
+
+ echo html_writer::div(
+ html_writer::link(new moodle_url('/course/view.php', array('id' => $id)),
+ get_string('continue')));
+} else {
+ // Display form.
+ $mform->display();
+}
+
+// Finish page.
+echo $OUTPUT->footer();
diff --git a/admin/tool/generator/settings.php b/admin/tool/generator/settings.php
new file mode 100644
index 00000000000..39a40aeced8
--- /dev/null
+++ b/admin/tool/generator/settings.php
@@ -0,0 +1,32 @@
+.
+
+/**
+ * Admin settings.
+ *
+ * @package tool_generator
+ * @copyright 2013 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die;
+
+if ($hassiteconfig) {
+ $ADMIN->add('development', new admin_externalpage('toolgenerator',
+ get_string('maketestcourse', 'tool_generator'),
+ $CFG->wwwroot . '/' . $CFG->admin . '/tool/generator/maketestcourse.php'));
+}
+
diff --git a/admin/tool/generator/tests/maketestcourse_test.php b/admin/tool/generator/tests/maketestcourse_test.php
new file mode 100644
index 00000000000..3b2244d284b
--- /dev/null
+++ b/admin/tool/generator/tests/maketestcourse_test.php
@@ -0,0 +1,110 @@
+.
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Automated unit testing. This tests the 'make large course' backend,
+ * using the 'XS' option so that it completes quickly.
+ *
+ * @package tool_generator
+ * @copyright 2013 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class tool_generator_maketestcourse_testcase extends advanced_testcase {
+ /**
+ * Creates a small test course and checks all the components have been put in place.
+ */
+ public function test_make_xs_course() {
+ global $DB;
+
+ $this->resetAfterTest();
+ $this->setAdminUser();
+
+ // Create the XS course.
+ $backend = new tool_generator_backend('TOOL_MAKELARGECOURSE_XS', 0, false);
+ $courseid = $backend->make();
+
+ // Get course details.
+ $course = get_course($courseid);
+ $context = context_course::instance($courseid);
+ $modinfo = get_fast_modinfo($course);
+
+ // Check sections (just section 0 plus one other).
+ $this->assertEquals(2, count($modinfo->get_section_info_all()));
+
+ // Check user is enrolled.
+ $users = get_enrolled_users($context);
+ $this->assertEquals(1, count($users));
+ $this->assertEquals('tool_generator_000001', reset($users)->username);
+
+ // Check there's a page on the course.
+ $pages = $modinfo->get_instances_of('page');
+ $this->assertEquals(1, count($pages));
+
+ // Check there are small files.
+ $resources = $modinfo->get_instances_of('resource');
+ $ok = false;
+ foreach ($resources as $resource) {
+ if ($resource->sectionnum == 0) {
+ // The one in section 0 is the 'small files' resource.
+ $ok = true;
+ break;
+ }
+ }
+ $this->assertTrue($ok);
+
+ // Check it contains 2 files (the default txt and a dat file).
+ $fs = get_file_storage();
+ $resourcecontext = context_module::instance($resource->id);
+ $files = $fs->get_area_files($resourcecontext->id, 'mod_resource', 'content', false, 'filename', false);
+ $files = array_values($files);
+ $this->assertEquals(2, count($files));
+ $this->assertEquals('resource1.txt', $files[0]->get_filename());
+ $this->assertEquals('smallfile0.dat', $files[1]->get_filename());
+
+ // Check there's a single 'big' file (it's actually only 8KB).
+ $ok = false;
+ foreach ($resources as $resource) {
+ if ($resource->sectionnum == 1) {
+ $ok = true;
+ break;
+ }
+ }
+ $this->assertTrue($ok);
+
+ // Check it contains 2 files.
+ $resourcecontext = context_module::instance($resource->id);
+ $files = $fs->get_area_files($resourcecontext->id, 'mod_resource', 'content', false, 'filename', false);
+ $files = array_values($files);
+ $this->assertEquals(2, count($files));
+ $this->assertEquals('bigfile0.dat', $files[0]->get_filename());
+ $this->assertEquals('resource2.txt', $files[1]->get_filename());
+
+ // Get forum and count the number of posts on it.
+ $forums = $modinfo->get_instances_of('forum');
+ $forum = reset($forums);
+ $posts = $DB->count_records_sql("
+ SELECT
+ COUNT(1)
+ FROM
+ {forum_posts} fp
+ JOIN {forum_discussions} fd ON fd.id = fp.discussion
+ WHERE
+ fd.forum = ?", array($forum->instance));
+ $this->assertEquals(2, $posts);
+ }
+}
diff --git a/admin/tool/generator/version.php b/admin/tool/generator/version.php
index 0e012509f58..66aa6623795 100644
--- a/admin/tool/generator/version.php
+++ b/admin/tool/generator/version.php
@@ -17,16 +17,13 @@
/**
* Version details.
*
- * @package tool
- * @subpackage generator
- * @copyright 2009 Nicolas Connault
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @package tool_generator
+ * @copyright 2013 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2013050100; // The current plugin version (Date: YYYYMMDDXX)
-$plugin->requires = 2013050100; // Requires this Moodle version
-$plugin->component = 'tool_generator'; // Full name of the plugin (used for diagnostics)
-
-$plugin->maturity = MATURITY_ALPHA; // this version's maturity level
+$plugin->version = 2013080700;
+$plugin->requires = 2013080200;
+$plugin->component = 'tool_generator';
diff --git a/admin/tool/phpunit/webrunner.php b/admin/tool/phpunit/webrunner.php
index f697f7ee085..f52bf6b002e 100644
--- a/admin/tool/phpunit/webrunner.php
+++ b/admin/tool/phpunit/webrunner.php
@@ -34,7 +34,7 @@ $execute = optional_param('execute', 0, PARAM_BOOL);
navigation_node::override_active_url(new moodle_url('/admin/tool/phpunit/index.php'));
admin_externalpage_setup('toolphpunitwebrunner');
-if (!debugging('', DEBUG_DEVELOPER)) {
+if (!$CFG->debugdeveloper) {
error('Not available on production sites, sorry.');
}
@@ -82,7 +82,7 @@ if ($execute) {
if ($code != 0) {
tool_phpunit_problem('Can not initialize database');
}
- $CFG->debug = 0; // no pesky redirect warning, we really want to redirect
+ set_debugging(DEBUG_NONE, false); // Hack: no redirect warning, we really want to redirect.
redirect(new moodle_url($PAGE->url, array('execute'=>1, 'tespath'=>$testpath, 'testclass'=>$testclass, 'sesskey'=>sesskey())), 'Reloading page');
echo $OUTPUT->footer();
die();
@@ -103,7 +103,7 @@ if ($execute) {
if ($code != 0) {
tool_phpunit_problem('Can not initialize database');
}
- $CFG->debug = 0; // no pesky redirect warning, we really want to redirect
+ set_debugging(DEBUG_NONE, false); // Hack: no redirect warning, we really want to redirect.
redirect(new moodle_url($PAGE->url, array('execute'=>1, 'tespath'=>$testpath, 'testclass'=>$testclass, 'sesskey'=>sesskey())), 'Reloading page');
die();
diff --git a/admin/tool/uploadcourse/classes/helper.php b/admin/tool/uploadcourse/classes/helper.php
index ebd0b75b5f6..2873f3b783f 100644
--- a/admin/tool/uploadcourse/classes/helper.php
+++ b/admin/tool/uploadcourse/classes/helper.php
@@ -37,31 +37,6 @@ require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
*/
class tool_uploadcourse_helper {
- /**
- * Remove the restore content from disk and cache.
- *
- * @return void
- */
- public static function clean_restore_content() {
- global $CFG;
-
- // There are some sloppy unclosed file handles in backup/restore code,
- // let's hope somebody unset all controllers before calling this
- // and destroy magic will close all remaining open file handles,
- // otherwise Windows will fail deleting the directory.
- gc_collect_cycles();
-
- if (!empty($CFG->keeptempdirectoriesonbackup)) {
- $cache = cache::make('tool_uploadcourse', 'helper');
- $backupids = (array) $cache->get('backupids');
- foreach ($backupids as $cachekey => $backupid) {
- $cache->delete($cachekey);
- fulldelete("$CFG->tempdir/backup/$backupid/");
- }
- $cache->delete('backupids');
- }
- }
-
/**
* Generate a shortname based on a template.
*
@@ -216,7 +191,9 @@ class tool_uploadcourse_helper {
* Get the restore content tempdir.
*
* The tempdir is the sub directory in which the backup has been extracted.
- * This caches the result for better performance.
+ *
+ * This caches the result for better performance, but $CFG->keeptempdirectoriesonbackup
+ * needs to be enabled, otherwise the cache is ignored.
*
* @param string $backupfile path to a backup file.
* @param string $shortname shortname of a course.
@@ -229,6 +206,10 @@ class tool_uploadcourse_helper {
$cachekey = null;
if (!empty($backupfile)) {
$backupfile = realpath($backupfile);
+ if (empty($backupfile) || !is_readable($backupfile)) {
+ $errors['cannotreadbackupfile'] = new lang_string('cannotreadbackupfile', 'tool_uploadcourse');
+ return false;
+ }
$cachekey = 'backup_path:' . $backupfile;
} else if (!empty($shortname) || is_numeric($shortname)) {
$cachekey = 'backup_sn:' . $shortname;
@@ -238,23 +219,27 @@ class tool_uploadcourse_helper {
return false;
}
- $cache = cache::make('tool_uploadcourse', 'helper');
- if (($backupid = $cache->get($cachekey)) === false) {
- // Use false instead of null because it would consider that the cache
- // key has not been set.
- $backupid = false;
+ // If $CFG->keeptempdirectoriesonbackup is not set to true, any restore happening would
+ // automatically delete the backup directory... causing the cache to return an unexisting directory.
+ $usecache = !empty($CFG->keeptempdirectoriesonbackup);
+ if ($usecache) {
+ $cache = cache::make('tool_uploadcourse', 'helper');
+ }
+
+ // If we don't use the cache, or if we do and not set, or the directory doesn't exist any more.
+ if (!$usecache || (($backupid = $cache->get($cachekey)) === false || !is_dir("$CFG->tempdir/backup/$backupid"))) {
+
+ // Use null instead of false because it would consider that the cache key has not been set.
+ $backupid = null;
+
if (!empty($backupfile)) {
- if (!is_readable($backupfile)) {
- $errors['cannotreadbackupfile'] = new lang_string('cannotreadbackupfile', 'tool_uploadcourse');
- } else {
- // Extracting the backup file.
- $packer = get_file_packer('application/vnd.moodle.backup');
- $backupid = restore_controller::get_tempdir_name(SITEID, $USER->id);
- $path = "$CFG->tempdir/backup/$backupid/";
- $result = $packer->extract_to_pathname($backupfile, $path);
- if (!$result) {
- $errors['invalidbackupfile'] = new lang_string('invalidbackupfile', 'tool_uploadcourse');
- }
+ // Extracting the backup file.
+ $packer = get_file_packer('application/vnd.moodle.backup');
+ $backupid = restore_controller::get_tempdir_name(SITEID, $USER->id);
+ $path = "$CFG->tempdir/backup/$backupid/";
+ $result = $packer->extract_to_pathname($backupfile, $path);
+ if (!$result) {
+ $errors['invalidbackupfile'] = new lang_string('invalidbackupfile', 'tool_uploadcourse');
}
} else if (!empty($shortname) || is_numeric($shortname)) {
// Creating restore from an existing course.
@@ -270,14 +255,15 @@ class tool_uploadcourse_helper {
new lang_string('coursetorestorefromdoesnotexist', 'tool_uploadcourse');
}
}
- $cache->set($cachekey, $backupid);
- // Store all the directories to be able to remove them in self::clean_restore_content().
- $backupids = (array) $cache->get('backupids');
- $backupids[$cachekey] = $backupid;
- $cache->set('backupids', $backupids);
+ if ($usecache) {
+ $cache->set($cachekey, $backupid);
+ }
}
+ if ($backupid === null) {
+ $backupid = false;
+ }
return $backupid;
}
diff --git a/admin/tool/uploadcourse/classes/processor.php b/admin/tool/uploadcourse/classes/processor.php
index 94ab8a7ec7e..79b10ed6263 100644
--- a/admin/tool/uploadcourse/classes/processor.php
+++ b/admin/tool/uploadcourse/classes/processor.php
@@ -223,8 +223,6 @@ class tool_uploadcourse_processor {
$tracker->finish();
$tracker->results($total, $created, $updated, $deleted, $errors);
-
- $this->remove_restore_content();
}
/**
@@ -349,20 +347,10 @@ class tool_uploadcourse_processor {
}
$tracker->finish();
- $this->remove_restore_content();
return $preview;
}
- /**
- * Delete the restore object.
- *
- * @return void
- */
- protected function remove_restore_content() {
- tool_uploadcourse_helper::clean_restore_content();
- }
-
/**
* Reset the current process.
*
diff --git a/admin/tool/uploadcourse/classes/step2_form.php b/admin/tool/uploadcourse/classes/step2_form.php
index 7464e4ea627..32886ef234c 100644
--- a/admin/tool/uploadcourse/classes/step2_form.php
+++ b/admin/tool/uploadcourse/classes/step2_form.php
@@ -58,11 +58,12 @@ class tool_uploadcourse_step2_form extends tool_uploadcourse_base_form {
$mform->disabledIf('options[shortnametemplate]', 'options[mode]', 'eq', tool_uploadcourse_processor::MODE_CREATE_OR_UPDATE);
$mform->disabledIf('options[shortnametemplate]', 'options[mode]', 'eq', tool_uploadcourse_processor::MODE_UPDATE_ONLY);
+ // Restore file is not in the array options on purpose, because formslib can't handle it!
$contextid = $this->_customdata['contextid'];
$mform->addElement('hidden', 'contextid', $contextid);
$mform->setType('contextid', PARAM_INT);
- $mform->addElement('filepicker', 'options[restorefile]', get_string('templatefile', 'tool_uploadcourse'));
- $mform->addHelpButton('options[restorefile]', 'templatefile', 'tool_uploadcourse');
+ $mform->addElement('filepicker', 'restorefile', get_string('templatefile', 'tool_uploadcourse'));
+ $mform->addHelpButton('restorefile', 'templatefile', 'tool_uploadcourse');
$mform->addElement('text', 'options[templatecourse]', get_string('coursetemplatename', 'tool_uploadcourse'));
$mform->setType('options[templatecourse]', PARAM_TEXT);
@@ -188,33 +189,4 @@ class tool_uploadcourse_step2_form extends tool_uploadcourse_base_form {
$mform->closeHeaderBefore('buttonar');
}
- /**
- * Server side validation.
- * @param array $data - form data
- * @param object $files - form files
- * @return array $errors - form errors
- */
- public function validation($data, $files) {
- $errors = parent::validation($data, $files);
- $columns = $this->_customdata['columns'];
- $optype = $data['options']['mode'];
-
- // Look for other required data.
- if ($optype != tool_uploadcourse_processor::MODE_UPDATE_ONLY) {
- if (!in_array('fullname', $columns)) {
- if (isset($errors['mode'])) {
- $errors['mode'] .= ' ';
- }
- $errors['mode'] .= get_string('missingfield', 'error', 'fullname');
- }
- if (!in_array('summary', $columns)) {
- if (isset($errors['mode'])) {
- $errors['mode'] .= ' ';
- }
- $errors['mode'] .= get_string('missingfield', 'error', 'summary');
- }
- }
-
- return $errors;
- }
}
diff --git a/admin/tool/uploadcourse/index.php b/admin/tool/uploadcourse/index.php
index 667551efd44..0f290e23ba0 100644
--- a/admin/tool/uploadcourse/index.php
+++ b/admin/tool/uploadcourse/index.php
@@ -78,6 +78,13 @@ if ($form2data = $mform2->is_cancelled()) {
$options = (array) $form2data->options;
$defaults = (array) $form2data->defaults;
+
+ // Restorefile deserves its own logic because formslib does not really appreciate
+ // when the name of a filepicker is an array...
+ $options['restorefile'] = '';
+ if (!empty($form2data->restorefile)) {
+ $options['restorefile'] = $mform2->save_temp_file('restorefile');
+ }
$processor = new tool_uploadcourse_processor($cir, $options, $defaults);
echo $OUTPUT->header();
@@ -91,6 +98,11 @@ if ($form2data = $mform2->is_cancelled()) {
echo $OUTPUT->continue_button($returnurl);
}
+ // Deleting the file after processing or preview.
+ if (!empty($options['restorefile'])) {
+ @unlink($options['restorefile']);
+ }
+
} else {
$processor = new tool_uploadcourse_processor($cir, $form1data->options, array());
echo $OUTPUT->header();
diff --git a/admin/tool/uploadcourse/tests/course_test.php b/admin/tool/uploadcourse/tests/course_test.php
index ee979b42ea8..3459c4a3cf9 100644
--- a/admin/tool/uploadcourse/tests/course_test.php
+++ b/admin/tool/uploadcourse/tests/course_test.php
@@ -635,6 +635,23 @@ class tool_uploadcourse_course_testcase extends advanced_testcase {
}
$this->assertTrue($found);
+ // Restoring twice from the same course should work.
+ $data = array('shortname' => 'B1', 'templatecourse' => $c1->shortname, 'summary' => 'B', 'category' => 1,
+ 'fullname' => 'B1');
+ $co = new tool_uploadcourse_course($mode, $updatemode, $data);
+ $this->assertTrue($co->prepare());
+ $co->proceed();
+ $course = $DB->get_record('course', array('shortname' => 'B1'));
+ $modinfo = get_fast_modinfo($course);
+ $found = false;
+ foreach ($modinfo->get_cms() as $cmid => $cm) {
+ if ($cm->modname == 'forum' && $cm->name == $c1f1->name) {
+ $found = true;
+ break;
+ }
+ }
+ $this->assertTrue($found);
+
// Restore the time limit to prevent warning.
set_time_limit(0);
}
@@ -668,6 +685,25 @@ class tool_uploadcourse_course_testcase extends advanced_testcase {
}
$this->assertTrue($found);
+ // Restoring twice from the same file should work.
+ $data = array('shortname' => 'B1', 'backupfile' => __DIR__ . '/fixtures/backup.mbz',
+ 'summary' => 'B', 'category' => 1, 'fullname' => 'B1');
+ $co = new tool_uploadcourse_course($mode, $updatemode, $data);
+ $this->assertTrue($co->prepare());
+ $co->proceed();
+ $course = $DB->get_record('course', array('shortname' => 'B1'));
+ $modinfo = get_fast_modinfo($course);
+ $found = false;
+ foreach ($modinfo->get_cms() as $cmid => $cm) {
+ if ($cm->modname == 'glossary' && $cm->name == 'Imported Glossary') {
+ $found = true;
+ } else if ($cm->modname == 'forum' && $cm->name == $c1f1->name) {
+ // We should not find this!
+ $this->assertTrue(false);
+ }
+ }
+ $this->assertTrue($found);
+
// Restore the time limit to prevent warning.
set_time_limit(0);
}
diff --git a/admin/tool/uploadcourse/tests/helper_test.php b/admin/tool/uploadcourse/tests/helper_test.php
index 31e53e04d61..ae42a1e2a43 100644
--- a/admin/tool/uploadcourse/tests/helper_test.php
+++ b/admin/tool/uploadcourse/tests/helper_test.php
@@ -141,6 +141,9 @@ class tool_uploadcourse_helper_testcase extends advanced_testcase {
$bc->destroy();
unset($bc); // File logging is a mess, we can only try to rely on gc to close handles.
+ $oldcfg = isset($CFG->keeptempdirectoriesonbackup) ? $CFG->keeptempdirectoriesonbackup : false;
+ $CFG->keeptempdirectoriesonbackup = true;
+
// Checking restore dir.
$dir = tool_uploadcourse_helper::get_restore_content_dir($c1backupfile, null);
$bcinfo = backup_general_helper::get_backup_information($dir);
@@ -179,18 +182,26 @@ class tool_uploadcourse_helper_testcase extends advanced_testcase {
$this->assertFalse($dir);
$this->assertArrayHasKey('coursetorestorefromdoesnotexist', $errors);
- // Cleaning content directories.
- $oldcfg = isset($CFG->keeptempdirectoriesonbackup) ? $CFG->keeptempdirectoriesonbackup : false;
- $dir = "$CFG->tempdir/backup/$dir";
- $this->assertTrue(file_exists($dir));
-
+ // Trying again without caching. $CFG->keeptempdirectoriesonbackup is required for caching.
$CFG->keeptempdirectoriesonbackup = false;
- tool_uploadcourse_helper::clean_restore_content();
- $this->assertTrue(file_exists($dir));
- $CFG->keeptempdirectoriesonbackup = true;
- tool_uploadcourse_helper::clean_restore_content();
- $this->assertFalse(file_exists($dir));
+ // Checking restore dir.
+ $dir = tool_uploadcourse_helper::get_restore_content_dir($c1backupfile, null);
+ $dir2 = tool_uploadcourse_helper::get_restore_content_dir($c1backupfile, null);
+ $this->assertNotEquals($dir, $dir2);
+
+ // Checking with a shortname.
+ $dir = tool_uploadcourse_helper::get_restore_content_dir(null, $c1->shortname);
+ $dir2 = tool_uploadcourse_helper::get_restore_content_dir(null, $c1->shortname);
+ $this->assertNotEquals($dir, $dir2);
+
+ // Get a course that does not exist.
+ $errors = array();
+ $dir = tool_uploadcourse_helper::get_restore_content_dir(null, 'DoesNotExist', $errors);
+ $this->assertFalse($dir);
+ $this->assertArrayHasKey('coursetorestorefromdoesnotexist', $errors);
+ $dir2 = tool_uploadcourse_helper::get_restore_content_dir(null, 'DoesNotExist', $errors);
+ $this->assertEquals($dir, $dir2);
$CFG->keeptempdirectoriesonbackup = $oldcfg;
diff --git a/auth/cas/cli/sync_users.php b/auth/cas/cli/sync_users.php
index 33cf33abbce..a02c32f16c3 100644
--- a/auth/cas/cli/sync_users.php
+++ b/auth/cas/cli/sync_users.php
@@ -48,7 +48,7 @@ require(dirname(dirname(dirname(dirname(__FILE__)))).'/config.php');
require_once($CFG->dirroot.'/course/lib.php');
// Ensure errors are well explained
-$CFG->debug = DEBUG_NORMAL;
+set_debugging(DEBUG_DEVELOPER, true);
if (!is_enabled_auth('cas')) {
error_log('[AUTH CAS] '.get_string('pluginnotenabled', 'auth_ldap'));
diff --git a/auth/ldap/cli/sync_users.php b/auth/ldap/cli/sync_users.php
index ee155384962..6898293a2e9 100644
--- a/auth/ldap/cli/sync_users.php
+++ b/auth/ldap/cli/sync_users.php
@@ -52,7 +52,7 @@ require(dirname(dirname(dirname(dirname(__FILE__)))).'/config.php'); // global m
require_once($CFG->dirroot.'/course/lib.php');
// Ensure errors are well explained
-$CFG->debug = DEBUG_NORMAL;
+set_debugging(DEBUG_DEVELOPER, true);
if (!is_enabled_auth('ldap')) {
error_log('[AUTH LDAP] '.get_string('pluginnotenabled', 'auth_ldap'));
diff --git a/backup/import.php b/backup/import.php
index 6dd503871d4..fbd919a583c 100644
--- a/backup/import.php
+++ b/backup/import.php
@@ -113,31 +113,46 @@ if ($backup->get_stage() == backup_ui::STAGE_FINAL) {
// Mark the UI finished.
$rc->finish_ui();
// Execute prechecks
+ $warnings = false;
if (!$rc->execute_precheck()) {
$precheckresults = $rc->get_precheck_results();
- if (is_array($precheckresults) && !empty($precheckresults['errors'])) {
- fulldelete($tempdestination);
+ if (is_array($precheckresults)) {
+ if (!empty($precheckresults['errors'])) { // If errors are found, terminate the import.
+ fulldelete($tempdestination);
- echo $OUTPUT->header();
- echo $renderer->precheck_notices($precheckresults);
- echo $OUTPUT->continue_button(new moodle_url('/course/view.php', array('id'=>$course->id)));
- echo $OUTPUT->footer();
- die();
+ echo $OUTPUT->header();
+ echo $renderer->precheck_notices($precheckresults);
+ echo $OUTPUT->continue_button(new moodle_url('/course/view.php', array('id'=>$course->id)));
+ echo $OUTPUT->footer();
+ die();
+ }
+ if (!empty($precheckresults['warnings'])) { // If warnings are found, go ahead but display warnings later.
+ $warnings = $precheckresults['warnings'];
+ }
}
- } else {
- if ($restoretarget == backup::TARGET_CURRENT_DELETING || $restoretarget == backup::TARGET_EXISTING_DELETING) {
- restore_dbops::delete_course_content($course->id);
- }
- // Execute the restore
- $rc->execute_plan();
}
+ if ($restoretarget == backup::TARGET_CURRENT_DELETING || $restoretarget == backup::TARGET_EXISTING_DELETING) {
+ restore_dbops::delete_course_content($course->id);
+ }
+ // Execute the restore.
+ $rc->execute_plan();
// Delete the temp directory now
fulldelete($tempdestination);
// Display a notification and a continue button
echo $OUTPUT->header();
- echo $OUTPUT->notification(get_string('importsuccess', 'backup'),'notifysuccess');
+ if ($warnings) {
+ echo $OUTPUT->box_start();
+ echo $OUTPUT->notification(get_string('warning'), 'notifywarning');
+ echo html_writer::start_tag('ul', array('class'=>'list'));
+ foreach ($warnings as $warning) {
+ echo html_writer::tag('li', $warning);
+ }
+ echo html_writer::end_tag('ul');
+ echo $OUTPUT->box_end();
+ }
+ echo $OUTPUT->notification(get_string('importsuccess', 'backup'), 'notifysuccess');
echo $OUTPUT->continue_button(new moodle_url('/course/view.php', array('id'=>$course->id)));
echo $OUTPUT->footer();
diff --git a/backup/moodle2/restore_qtype_plugin.class.php b/backup/moodle2/restore_qtype_plugin.class.php
index 99365bad2f2..60f599a38b9 100644
--- a/backup/moodle2/restore_qtype_plugin.class.php
+++ b/backup/moodle2/restore_qtype_plugin.class.php
@@ -35,6 +35,18 @@ defined('MOODLE_INTERNAL') || die();
*/
abstract class restore_qtype_plugin extends restore_plugin {
+ /*
+ * A simple answer to id cache for a single questions answers.
+ * @var array
+ */
+ private $questionanswercache = array();
+
+ /*
+ * The id of the current question in the questionanswercache.
+ * @var int
+ */
+ private $questionanswercacheid = null;
+
/**
* Add to $paths the restore_path_elements needed
* to handle question_answers for a given question
@@ -147,38 +159,32 @@ abstract class restore_qtype_plugin extends restore_plugin {
// The question existed, we need to map the existing question_answers
} else {
- // Look in question_answers by answertext matching
- $sql = 'SELECT id
- FROM {question_answers}
- WHERE question = ?
- AND ' . $DB->sql_compare_text('answer', 255) . ' = ' . $DB->sql_compare_text('?', 255);
- $params = array($newquestionid, $data->answertext);
- $newitemid = $DB->get_field_sql($sql, $params);
-
- // Not able to find the answer, let's try cleaning the answertext
- // of all the question answers in DB as slower fallback. MDL-30018.
- if (!$newitemid) {
+ // Have we cached the current question?
+ if ($this->questionanswercacheid !== $newquestionid) {
+ // The question changed, purge and start again!
+ $this->questionanswercache = array();
$params = array('question' => $newquestionid);
$answers = $DB->get_records('question_answers', $params, '', 'id, answer');
+ $this->questionanswercacheid = $newquestionid;
+ // Cache all cleaned answers for a simple text match.
foreach ($answers as $answer) {
- // Clean in the same way than {@link xml_writer::xml_safe_utf8()}.
+ // MDL-30018: Clean in the same way as {@link xml_writer::xml_safe_utf8()}.
$clean = preg_replace('/[\x-\x8\xb-\xc\xe-\x1f\x7f]/is','', $answer->answer); // Clean CTRL chars.
$clean = preg_replace("/\r\n|\r/", "\n", $clean); // Normalize line ending.
- if ($clean === $data->answertext) {
- $newitemid = $data->id;
- }
+ $this->questionanswercache[$clean] = $answer->id;
}
}
- // If we haven't found the newitemid, something has gone really wrong, question in DB
- // is missing answers, exception
- if (!$newitemid) {
+ if (!isset($this->questionanswercache[$data->answertext])) {
+ // If we haven't found the matching answer, something has gone really wrong, the question in the DB
+ // is missing answers, throw an exception.
$info = new stdClass();
$info->filequestionid = $oldquestionid;
$info->dbquestionid = $newquestionid;
$info->answer = $data->answertext;
throw new restore_step_exception('error_question_answers_missing_in_db', $info);
}
+ $newitemid = $this->questionanswercache[$data->answertext];
}
// Create mapping (we'll use this intensively when restoring question_states. And also answerfeedback files)
$this->set_mapping('question_answer', $oldid, $newitemid);
diff --git a/backup/util/factories/backup_factory.class.php b/backup/util/factories/backup_factory.class.php
index e2143cd05e9..6f8ba31d0c8 100644
--- a/backup/util/factories/backup_factory.class.php
+++ b/backup/util/factories/backup_factory.class.php
@@ -40,7 +40,7 @@ abstract class backup_factory {
global $CFG;
$dfltloglevel = backup::LOG_WARNING; // Default logging level
- if (debugging('', DEBUG_DEVELOPER)) { // Debug developer raises default logging level
+ if ($CFG->debugdeveloper) { // Debug developer raises default logging level
$dfltloglevel = backup::LOG_DEBUG;
}
diff --git a/backup/util/factories/tests/factories_test.php b/backup/util/factories/tests/factories_test.php
index 54dc4e99fd7..2a3fa1a55eb 100644
--- a/backup/util/factories/tests/factories_test.php
+++ b/backup/util/factories/tests/factories_test.php
@@ -94,7 +94,6 @@ class backup_factories_testcase extends advanced_testcase {
// Instantiate with debugging enabled and $CFG->backup_error_log_logger_level not set
$CFG->debugdisplay = true;
- $CFG->debug = DEBUG_DEVELOPER;
unset($CFG->backup_error_log_logger_level);
$logger1 = backup_factory::get_logger_chain(backup::INTERACTIVE_YES, backup::EXECUTION_INMEDIATE, 'test');
$this->assertTrue($logger1 instanceof error_log_logger); // 1st logger is error_log_logger
diff --git a/backup/util/plan/restore_plan.class.php b/backup/util/plan/restore_plan.class.php
index 9943e0bd0bc..c9dd8fb77ee 100644
--- a/backup/util/plan/restore_plan.class.php
+++ b/backup/util/plan/restore_plan.class.php
@@ -157,15 +157,18 @@ class restore_plan extends base_plan implements loggable {
parent::execute();
$this->controller->set_status(backup::STATUS_FINISHED_OK);
- events_trigger('course_restored', (object) array(
- 'courseid' => $this->get_courseid(), // The new course
- 'userid' => $this->get_userid(), // User doing the restore
- 'type' => $this->controller->get_type(), // backup::TYPE_* constant
- 'target' => $this->controller->get_target(), // backup::TARGET_* constant
- 'mode' => $this->controller->get_mode(), // backup::MODE_* constant
- 'operation' => $this->controller->get_operation(), // backup::OPERATION_* constant
- 'samesite' => $this->controller->is_samesite(),
+ // Trigger a course restored event.
+ $event = \core\event\course_restored::create(array(
+ 'objectid' => $this->get_courseid(),
+ 'userid' => $this->get_userid(),
+ 'context' => context_course::instance($this->get_courseid()),
+ 'other' => array('type' => $this->controller->get_type(),
+ 'target' => $this->controller->get_target(),
+ 'mode' => $this->controller->get_mode(),
+ 'operation' => $this->controller->get_operation(),
+ 'samesite' => $this->controller->is_samesite())
));
+ $event->trigger();
}
/**
diff --git a/badges/lib/awardlib.php b/badges/lib/awardlib.php
index 30987e089a9..8bccf53be4b 100644
--- a/badges/lib/awardlib.php
+++ b/badges/lib/awardlib.php
@@ -26,7 +26,6 @@
defined('MOODLE_INTERNAL') || die();
-require_once(dirname(dirname(dirname(__FILE__))) . '/config.php');
require_once($CFG->libdir . '/badgeslib.php');
require_once($CFG->dirroot . '/user/selector/lib.php');
diff --git a/badges/renderer.php b/badges/renderer.php
index f6abcb699cd..088b443ca96 100644
--- a/badges/renderer.php
+++ b/badges/renderer.php
@@ -26,7 +26,6 @@
require_once($CFG->libdir . '/badgeslib.php');
require_once($CFG->libdir . '/tablelib.php');
-require_once($CFG->dirroot . '/user/filters/lib.php');
/**
* Standard HTML output renderer for badges
diff --git a/blocks/moodleblock.class.php b/blocks/moodleblock.class.php
index 9d9b289ffd0..461baf15972 100644
--- a/blocks/moodleblock.class.php
+++ b/blocks/moodleblock.class.php
@@ -416,6 +416,9 @@ class block_base {
'class' => 'block_' . $this->name(). ' block',
'role' => $this->get_aria_role()
);
+ if ($this->hide_header()) {
+ $attributes['class'] .= ' no-header';
+ }
if ($this->instance_can_be_docked() && get_user_preferences('docked_block_instance_'.$this->instance->id, 0)) {
$attributes['class'] .= ' dock_on_load';
}
diff --git a/blocks/tags/block_tags.php b/blocks/tags/block_tags.php
index 10356140fc6..6b1a7930da1 100644
--- a/blocks/tags/block_tags.php
+++ b/blocks/tags/block_tags.php
@@ -110,12 +110,12 @@ class block_tags extends block_base {
$content = '';
$moretags = new moodle_url('/tag/coursetags_more.php', array('show'=>$tagtype));
if ($tagtype == 'all') {
- $tags = coursetag_get_tags(0, 0, $this->config->tagtype, $this->config->numberoftags, 'name');
+ $tags = coursetag_get_tags(0, 0, $this->config->tagtype, $this->config->numberoftags);
} else if ($tagtype == 'course') {
- $tags = coursetag_get_tags($this->page->course->id, 0, $this->config->tagtype, $this->config->numberoftags, 'name');
+ $tags = coursetag_get_tags($this->page->course->id, 0, $this->config->tagtype, $this->config->numberoftags);
$moretags->param('courseid', $this->page->course->id);
} else if ($tagtype == 'my') {
- $tags = coursetag_get_tags(0, $USER->id, $this->config->tagtype, $this->config->numberoftags, 'name');
+ $tags = coursetag_get_tags(0, $USER->id, $this->config->tagtype, $this->config->numberoftags);
}
$tagcloud = tag_print_cloud($tags, 150, true);
if (!$tagcloud) {
diff --git a/cache/stores/file/lib.php b/cache/stores/file/lib.php
index adc1a9bda16..4830d71765d 100644
--- a/cache/stores/file/lib.php
+++ b/cache/stores/file/lib.php
@@ -336,6 +336,7 @@ class cachestore_file extends cache_store implements cache_is_key_aware, cache_i
$filename = $key.'.cache';
$file = $this->file_path_for_key($key);
$ttl = $this->definition->get_ttl();
+ $maxtime = 0;
if ($ttl) {
$maxtime = cache::now() - $ttl;
}
diff --git a/cache/tests/administration_helper_test.php b/cache/tests/administration_helper_test.php
index 6a1630d7c69..c30ae0c648e 100644
--- a/cache/tests/administration_helper_test.php
+++ b/cache/tests/administration_helper_test.php
@@ -189,11 +189,8 @@ class core_cache_administration_helper_testcase extends advanced_testcase {
* Test the hash_key functionality.
*/
public function test_hash_key() {
- global $CFG;
-
- $currentdebugging = $CFG->debug;
-
- $CFG->debug = E_ALL;
+ $this->resetAfterTest();
+ set_debugging(DEBUG_ALL);
// First with simplekeys
$instance = cache_config_phpunittest::instance(true);
@@ -230,7 +227,5 @@ class core_cache_administration_helper_testcase extends advanced_testcase {
$result = cache_helper::hash_key('test/test', $definition);
$this->assertEquals(sha1($definition->generate_single_key_prefix().'-test/test'), $result);
-
- $CFG->debug = $currentdebugging;
}
}
diff --git a/calendar/lib.php b/calendar/lib.php
index 86c265f410f..7fed0c0ee76 100644
--- a/calendar/lib.php
+++ b/calendar/lib.php
@@ -1410,14 +1410,8 @@ function calendar_get_module_cached(&$coursecache, $modulename, $instance) {
* @return stdClass $coursecache[$courseid] return the specific course cache
*/
function calendar_get_course_cached(&$coursecache, $courseid) {
- global $COURSE, $DB;
-
if (!isset($coursecache[$courseid])) {
- if ($courseid == $COURSE->id) {
- $coursecache[$courseid] = $COURSE;
- } else {
- $coursecache[$courseid] = $DB->get_record('course', array('id'=>$courseid));
- }
+ $coursecache[$courseid] = get_course($courseid);
}
return $coursecache[$courseid];
}
diff --git a/calendar/tests/lib_test.php b/calendar/tests/lib_test.php
new file mode 100644
index 00000000000..c405b969a36
--- /dev/null
+++ b/calendar/tests/lib_test.php
@@ -0,0 +1,71 @@
+.
+
+/**
+ * Calendar lib unit tests
+ *
+ * @package core_calendar
+ * @copyright 2013 Dan Poltawski
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+global $CFG;
+require_once($CFG->dirroot . '/calendar/lib.php');
+
+/**
+ * Unit tests for calendar lib
+ *
+ * @package core_calendar
+ * @copyright 2013 Dan Poltawski
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_calendar_lib_testcase extends advanced_testcase {
+
+ public function test_calendar_get_course_cached() {
+ $this->resetAfterTest(true);
+
+ // Setup some test courses.
+ $course1 = $this->getDataGenerator()->create_course();
+ $course2 = $this->getDataGenerator()->create_course();
+ $course3 = $this->getDataGenerator()->create_course();
+
+ // Load courses into cache.
+ $coursecache = null;
+ calendar_get_course_cached($coursecache, $course1->id);
+ calendar_get_course_cached($coursecache, $course2->id);
+ calendar_get_course_cached($coursecache, $course3->id);
+
+ // Verify the cache.
+ $this->assertArrayHasKey($course1->id, $coursecache);
+ $cachedcourse1 = $coursecache[$course1->id];
+ $this->assertEquals($course1->id, $cachedcourse1->id);
+ $this->assertEquals($course1->shortname, $cachedcourse1->shortname);
+ $this->assertEquals($course1->fullname, $cachedcourse1->fullname);
+
+ $this->assertArrayHasKey($course2->id, $coursecache);
+ $cachedcourse2 = $coursecache[$course2->id];
+ $this->assertEquals($course2->id, $cachedcourse2->id);
+ $this->assertEquals($course2->shortname, $cachedcourse2->shortname);
+ $this->assertEquals($course2->fullname, $cachedcourse2->fullname);
+
+ $this->assertArrayHasKey($course3->id, $coursecache);
+ $cachedcourse3 = $coursecache[$course3->id];
+ $this->assertEquals($course3->id, $cachedcourse3->id);
+ $this->assertEquals($course3->shortname, $cachedcourse3->shortname);
+ $this->assertEquals($course3->fullname, $cachedcourse3->fullname);
+ }
+}
diff --git a/cohort/lib.php b/cohort/lib.php
index 61d56c955ce..51ac01c163f 100644
--- a/cohort/lib.php
+++ b/cohort/lib.php
@@ -57,7 +57,12 @@ function cohort_add_cohort($cohort) {
$cohort->id = $DB->insert_record('cohort', $cohort);
- events_trigger('cohort_added', $cohort);
+ $event = \core\event\cohort_created::create(array(
+ 'context' => context::instance_by_id($cohort->contextid),
+ 'objectid' => $cohort->id,
+ ));
+ $event->add_record_snapshot('cohort', $cohort);
+ $event->trigger();
return $cohort->id;
}
@@ -76,7 +81,12 @@ function cohort_update_cohort($cohort) {
$cohort->timemodified = time();
$DB->update_record('cohort', $cohort);
- events_trigger('cohort_updated', $cohort);
+ $event = \core\event\cohort_updated::create(array(
+ 'context' => context::instance_by_id($cohort->contextid),
+ 'objectid' => $cohort->id,
+ ));
+ $event->add_record_snapshot('cohort', $cohort);
+ $event->trigger();
}
/**
@@ -94,7 +104,12 @@ function cohort_delete_cohort($cohort) {
$DB->delete_records('cohort_members', array('cohortid'=>$cohort->id));
$DB->delete_records('cohort', array('id'=>$cohort->id));
- events_trigger('cohort_deleted', $cohort);
+ $event = \core\event\cohort_deleted::create(array(
+ 'context' => context::instance_by_id($cohort->contextid),
+ 'objectid' => $cohort->id,
+ ));
+ $event->add_record_snapshot('cohort', $cohort);
+ $event->trigger();
}
/**
@@ -141,7 +156,15 @@ function cohort_add_member($cohortid, $userid) {
$record->timeadded = time();
$DB->insert_record('cohort_members', $record);
- events_trigger('cohort_member_added', (object)array('cohortid'=>$cohortid, 'userid'=>$userid));
+ $cohort = $DB->get_record('cohort', array('id' => $cohortid), '*', MUST_EXIST);
+
+ $event = \core\event\cohort_member_added::create(array(
+ 'context' => context::instance_by_id($cohort->contextid),
+ 'objectid' => $cohortid,
+ 'relateduserid' => $userid,
+ ));
+ $event->add_record_snapshot('cohort', $cohort);
+ $event->trigger();
}
/**
@@ -154,7 +177,15 @@ function cohort_remove_member($cohortid, $userid) {
global $DB;
$DB->delete_records('cohort_members', array('cohortid'=>$cohortid, 'userid'=>$userid));
- events_trigger('cohort_member_removed', (object)array('cohortid'=>$cohortid, 'userid'=>$userid));
+ $cohort = $DB->get_record('cohort', array('id' => $cohortid), '*', MUST_EXIST);
+
+ $event = \core\event\cohort_member_removed::create(array(
+ 'context' => context::instance_by_id($cohort->contextid),
+ 'objectid' => $cohortid,
+ 'relateduserid' => $userid,
+ ));
+ $event->add_record_snapshot('cohort', $cohort);
+ $event->trigger();
}
/**
diff --git a/cohort/tests/cohortlib_test.php b/cohort/tests/cohortlib_test.php
index e6446e77edd..64f40c7e7b6 100644
--- a/cohort/tests/cohortlib_test.php
+++ b/cohort/tests/cohortlib_test.php
@@ -62,20 +62,50 @@ class core_cohort_cohortlib_testcase extends advanced_testcase {
$this->assertNotEmpty($newcohort->timecreated);
$this->assertSame($newcohort->component, '');
$this->assertSame($newcohort->timecreated, $newcohort->timemodified);
+ }
- try {
- $cohort = new stdClass();
- $cohort->contextid = context_system::instance()->id;
- $cohort->name = null;
- $cohort->idnumber = 'testid';
- $cohort->description = 'test cohort desc';
- $cohort->descriptionformat = FORMAT_HTML;
- cohort_add_cohort($cohort);
+ public function test_cohort_add_cohort_missing_name() {
+ $cohort = new stdClass();
+ $cohort->contextid = context_system::instance()->id;
+ $cohort->name = null;
+ $cohort->idnumber = 'testid';
+ $cohort->description = 'test cohort desc';
+ $cohort->descriptionformat = FORMAT_HTML;
- $this->fail('Exception expected when trying to add cohort without name');
- } catch (Exception $e) {
- $this->assertInstanceOf('coding_exception', $e);
- }
+ $this->setExpectedException('coding_exception', 'Missing cohort name in cohort_add_cohort().');
+ cohort_add_cohort($cohort);
+ }
+
+ public function test_cohort_add_cohort_event() {
+ $this->resetAfterTest();
+
+ // Setup cohort data structure.
+ $cohort = new stdClass();
+ $cohort->contextid = context_system::instance()->id;
+ $cohort->name = 'test cohort';
+ $cohort->idnumber = 'testid';
+ $cohort->description = 'test cohort desc';
+ $cohort->descriptionformat = FORMAT_HTML;
+
+ // Catch Events.
+ $sink = $this->redirectEvents();
+
+ // Perform the add operation.
+ $id = cohort_add_cohort($cohort);
+
+ // Capture the event.
+ $events = $sink->get_events();
+ $sink->close();
+
+ // Validate the event.
+ $this->assertCount(1, $events);
+ $event = $events[0];
+ $this->assertInstanceOf('\core\event\cohort_created', $event);
+ $this->assertEquals('cohort', $event->objecttable);
+ $this->assertEquals($id, $event->objectid);
+ $this->assertEquals($cohort->contextid, $event->contextid);
+ $this->assertEquals($cohort, $event->get_record_snapshot('cohort', $id));
+ $this->assertEventLegacyData($cohort, $event);
}
public function test_cohort_update_cohort() {
@@ -110,6 +140,44 @@ class core_cohort_cohortlib_testcase extends advanced_testcase {
$this->assertLessThanOrEqual(time(), $newcohort->timemodified);
}
+ public function test_cohort_update_cohort_event() {
+ global $DB;
+
+ $this->resetAfterTest();
+
+ // Setup the cohort data structure.
+ $cohort = new stdClass();
+ $cohort->contextid = context_system::instance()->id;
+ $cohort->name = 'test cohort';
+ $cohort->idnumber = 'testid';
+ $cohort->description = 'test cohort desc';
+ $cohort->descriptionformat = FORMAT_HTML;
+ $id = cohort_add_cohort($cohort);
+ $this->assertNotEmpty($id);
+
+ $cohort->name = 'test cohort 2';
+
+ // Catch Events.
+ $sink = $this->redirectEvents();
+
+ // Peform the update.
+ cohort_update_cohort($cohort);
+
+ $events = $sink->get_events();
+ $sink->close();
+
+ // Validate the event.
+ $this->assertCount(1, $events);
+ $event = $events[0];
+ $updatedcohort = $DB->get_record('cohort', array('id'=>$id));
+ $this->assertInstanceOf('\core\event\cohort_updated', $event);
+ $this->assertEquals('cohort', $event->objecttable);
+ $this->assertEquals($updatedcohort->id, $event->objectid);
+ $this->assertEquals($updatedcohort->contextid, $event->contextid);
+ $this->assertEquals($cohort, $event->get_record_snapshot('cohort', $id));
+ $this->assertEventLegacyData($cohort, $event);
+ }
+
public function test_cohort_delete_cohort() {
global $DB;
@@ -122,6 +190,31 @@ class core_cohort_cohortlib_testcase extends advanced_testcase {
$this->assertFalse($DB->record_exists('cohort', array('id'=>$cohort->id)));
}
+ public function test_cohort_delete_cohort_event() {
+
+ $this->resetAfterTest();
+
+ $cohort = $this->getDataGenerator()->create_cohort();
+
+ // Capture the events.
+ $sink = $this->redirectEvents();
+
+ // Perform the delete.
+ cohort_delete_cohort($cohort);
+
+ $events = $sink->get_events();
+ $sink->close();
+
+ // Validate the event structure.
+ $this->assertCount(1, $events);
+ $event = $events[0];
+ $this->assertInstanceOf('\core\event\cohort_deleted', $event);
+ $this->assertEquals('cohort', $event->objecttable);
+ $this->assertEquals($cohort->id, $event->objectid);
+ $this->assertEquals($cohort, $event->get_record_snapshot('cohort', $cohort->id));
+ $this->assertEventLegacyData($cohort, $event);
+ }
+
public function test_cohort_delete_category() {
global $DB;
@@ -151,6 +244,34 @@ class core_cohort_cohortlib_testcase extends advanced_testcase {
$this->assertTrue($DB->record_exists('cohort_members', array('cohortid'=>$cohort->id, 'userid'=>$user->id)));
}
+ public function test_cohort_add_member_event() {
+ global $USER;
+ $this->resetAfterTest();
+
+ // Setup the data.
+ $cohort = $this->getDataGenerator()->create_cohort();
+ $user = $this->getDataGenerator()->create_user();
+
+ // Capture the events.
+ $sink = $this->redirectEvents();
+
+ // Peform the add member operation.
+ cohort_add_member($cohort->id, $user->id);
+
+ $events = $sink->get_events();
+ $sink->close();
+
+ // Validate the event.
+ $this->assertCount(1, $events);
+ $event = $events[0];
+ $this->assertInstanceOf('\core\event\cohort_member_added', $event);
+ $this->assertEquals('cohort', $event->objecttable);
+ $this->assertEquals($cohort->id, $event->objectid);
+ $this->assertEquals($user->id, $event->relateduserid);
+ $this->assertEquals($USER->id, $event->userid);
+ $this->assertEventLegacyData((object) array('cohortid' => $cohort->id, 'userid' => $user->id), $event);
+ }
+
public function test_cohort_remove_member() {
global $DB;
@@ -166,6 +287,34 @@ class core_cohort_cohortlib_testcase extends advanced_testcase {
$this->assertFalse($DB->record_exists('cohort_members', array('cohortid'=>$cohort->id, 'userid'=>$user->id)));
}
+ public function test_cohort_remove_member_event() {
+ global $USER;
+ $this->resetAfterTest();
+
+ // Setup the data.
+ $cohort = $this->getDataGenerator()->create_cohort();
+ $user = $this->getDataGenerator()->create_user();
+ cohort_add_member($cohort->id, $user->id);
+
+ // Capture the events.
+ $sink = $this->redirectEvents();
+
+ // Peform the remove operation.
+ cohort_remove_member($cohort->id, $user->id);
+ $events = $sink->get_events();
+ $sink->close();
+
+ // Validate the event.
+ $this->assertCount(1, $events);
+ $event = $events[0];
+ $this->assertInstanceOf('\core\event\cohort_member_removed', $event);
+ $this->assertEquals('cohort', $event->objecttable);
+ $this->assertEquals($cohort->id, $event->objectid);
+ $this->assertEquals($user->id, $event->relateduserid);
+ $this->assertEquals($USER->id, $event->userid);
+ $this->assertEventLegacyData((object) array('cohortid' => $cohort->id, 'userid' => $user->id), $event);
+ }
+
public function test_cohort_is_member() {
global $DB;
diff --git a/config-dist.php b/config-dist.php
index 8c3b7339e76..0b2a9b1e7b9 100644
--- a/config-dist.php
+++ b/config-dist.php
@@ -464,10 +464,10 @@ $CFG->admin = 'admin';
// $CFG->debugusers = '2';
//
// Prevent theme caching
-// $CFG->themerev = -1; // NOT FOR PRODUCTION SERVERS!
+// $CFG->themedesignermode = true; // NOT FOR PRODUCTION SERVERS!
//
// Prevent JS caching
-// $CFG->jsrev = -1; // NOT FOR PRODUCTION SERVERS!
+// $CFG->cachejs = false; // NOT FOR PRODUCTION SERVERS!
//
// Prevent core_string_manager application caching
// $CFG->langstringcache = false; // NOT FOR PRODUCTION SERVERS!
diff --git a/course/completion.php b/course/completion.php
index 8c16a87f47d..12e4859fd61 100644
--- a/course/completion.php
+++ b/course/completion.php
@@ -134,8 +134,14 @@ if ($form->is_cancelled()){
$aggregation->setMethod($data->role_aggregation);
$aggregation->save();
- // Log changes.
- add_to_log($course->id, 'course', 'completion updated', 'completion.php?id='.$course->id);
+ // Trigger an event for course module completion changed.
+ $event = \core\event\course_completion_updated::create(
+ array(
+ 'courseid' => $course->id,
+ 'context' => context_course::instance($course->id)
+ )
+ );
+ $event->trigger();
// Redirect to the course main page.
$url = new moodle_url('/course/view.php', array('id' => $course->id));
diff --git a/course/delete.php b/course/delete.php
index 71bafca20b9..26c3de29cd9 100644
--- a/course/delete.php
+++ b/course/delete.php
@@ -63,10 +63,6 @@
print_error('confirmsesskeybad', 'error');
}
- // OK checks done, delete the course now.
-
- add_to_log(SITEID, "course", "delete", "view.php?id=$course->id", "$course->fullname (ID $course->id)");
-
$strdeletingcourse = get_string("deletingcourse", "", $courseshortname);
$PAGE->navbar->add($strdeletingcourse);
diff --git a/course/editsection.php b/course/editsection.php
index c75e3dc0ba4..54db9394333 100644
--- a/course/editsection.php
+++ b/course/editsection.php
@@ -25,6 +25,7 @@
require_once("../config.php");
require_once("lib.php");
+require_once($CFG->libdir . '/formslib.php');
require_once($CFG->libdir . '/conditionlib.php');
$id = required_param('id', PARAM_INT); // course_sections.id
@@ -40,7 +41,7 @@ require_login($course);
$context = context_course::instance($course->id);
require_capability('moodle/course:update', $context);
-// get section_info object with all availability options
+// Get section_info object with all availability options.
$sectioninfo = get_fast_modinfo($course)->get_section_info($sectionnum);
$editoroptions = array('context'=>$context ,'maxfiles' => EDITOR_UNLIMITED_FILES, 'maxbytes'=>$CFG->maxbytes, 'trusttext'=>false, 'noclean'=>true);
@@ -51,31 +52,44 @@ $mform = course_get_format($course->id)->editsection_form($PAGE->url,
$mform->set_data(convert_to_array($sectioninfo));
if ($mform->is_cancelled()){
- // form cancelled, return to course
+ // Form cancelled, return to course.
redirect(course_get_url($course, $section, array('sr' => $sectionreturn)));
} else if ($data = $mform->get_data()) {
- // data submitted and validated, update and return to course
+ // Data submitted and validated, update and return to course.
$DB->update_record('course_sections', $data);
rebuild_course_cache($course->id, true);
if (isset($data->section)) {
- // usually edit form does not change relative section number but just in case
+ // Usually edit form does not change relative section number but just in case.
$sectionnum = $data->section;
}
if (!empty($CFG->enableavailability)) {
- // Update grade and completion conditions
+ // Update grade and completion conditions.
$sectioninfo = get_fast_modinfo($course)->get_section_info($sectionnum);
condition_info_section::update_section_from_form($sectioninfo, $data);
rebuild_course_cache($course->id, true);
}
course_get_format($course->id)->update_section_format_options($data);
- add_to_log($course->id, "course", "editsection", "editsection.php?id=$id", "$sectionnum");
+ // Set section info, as this might not be present in form_data.
+ if (!isset($data->section)) {
+ $data->section = $sectionnum;
+ }
+ // Trigger an event for course section update.
+ $event = \core\event\course_section_updated::create(
+ array(
+ 'objectid' => $data->id,
+ 'courseid' => $course->id,
+ 'context' => $context,
+ 'other' => array('sectionnum' => $data->section)
+ )
+ );
+ $event->trigger();
+
$PAGE->navigation->clear_cache();
redirect(course_get_url($course, $section, array('sr' => $sectionreturn)));
}
-// the edit form is displayed for the first time or there was a validation
-// error on the previous step. Display the edit form:
+// The edit form is displayed for the first time or if there was validation error on the previous step.
$sectionname = get_section_name($course, $sectionnum);
$stredit = get_string('edita', '', " $sectionname");
$strsummaryof = get_string('summaryof', '', " $sectionname");
diff --git a/course/format/renderer.php b/course/format/renderer.php
index fe693152222..bfc5641b364 100644
--- a/course/format/renderer.php
+++ b/course/format/renderer.php
@@ -172,9 +172,11 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
// When on a section page, we only display the general section title, if title is not the default one
$hasnamesecpg = ($onsectionpage && ($section->section == 0 && !is_null($section->name)));
+ $classes = ' accesshide';
if ($hasnamenotsecpg || $hasnamesecpg) {
- $o.= $this->output->heading($this->section_title($section, $course), 3, 'sectionname');
+ $classes = '';
}
+ $o.= $this->output->heading($this->section_title($section, $course), 3, 'sectionname' . $classes);
$o.= html_writer::start_tag('div', array('class' => 'summary'));
$o.= $this->format_summary_text($section);
diff --git a/course/lib.php b/course/lib.php
index 31f539c0a18..e1e12409f75 100644
--- a/course/lib.php
+++ b/course/lib.php
@@ -28,7 +28,6 @@ defined('MOODLE_INTERNAL') || die;
require_once($CFG->libdir.'/completionlib.php');
require_once($CFG->libdir.'/filelib.php');
-require_once($CFG->dirroot.'/course/dnduploadlib.php');
require_once($CFG->dirroot.'/course/format/lib.php');
define('COURSE_MAX_LOGS_PER_PAGE', 1000); // records
@@ -959,7 +958,9 @@ function get_array_of_activities($courseid) {
$mod[$seq]->extraclasses = $info->extraclasses;
}
if (!empty($info->iconurl)) {
- $mod[$seq]->iconurl = $info->iconurl;
+ // Convert URL to string as it's easier to store. Also serialized object contains \0 byte and can not be written to Postgres DB.
+ $url = new moodle_url($info->iconurl);
+ $mod[$seq]->iconurl = $url->out(false);
}
if (!empty($info->onclick)) {
$mod[$seq]->onclick = $info->onclick;
@@ -1951,7 +1952,7 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) {
array('class' => 'editing_'. $actionname, 'data-action' => $actionname, 'data-nextgroupmode' => $nextgroupmode)
);
} else {
- $actions[$actionname] = new pix_icon($groupimage, $forcedgrouptitle, 'moodle', array('title' => '', 'class' => 'iconsmall'));
+ $actions[$actionname] = new pix_icon($groupimage, $forcedgrouptitle, 'moodle', array('class' => 'iconsmall'));
}
}
@@ -2026,14 +2027,14 @@ function course_allowed_module($course, $modname) {
* @return bool success
*/
function move_courses($courseids, $categoryid) {
- global $CFG, $DB, $OUTPUT;
+ global $DB;
if (empty($courseids)) {
- // nothing to do
+ // Nothing to do.
return;
}
- if (!$category = $DB->get_record('course_categories', array('id'=>$categoryid))) {
+ if (!$category = $DB->get_record('course_categories', array('id' => $categoryid))) {
return false;
}
@@ -2042,21 +2043,37 @@ function move_courses($courseids, $categoryid) {
$i = 1;
foreach ($courseids as $courseid) {
- if ($course = $DB->get_record('course', array('id'=>$courseid), 'id, category')) {
+ if ($dbcourse = $DB->get_record('course', array('id' => $courseid))) {
$course = new stdClass();
$course->id = $courseid;
$course->category = $category->id;
$course->sortorder = $category->sortorder + MAX_COURSES_IN_CATEGORY - $i++;
if ($category->visible == 0) {
- // hide the course when moving into hidden category,
- // do not update the visibleold flag - we want to get to previous state if somebody unhides the category
+ // Hide the course when moving into hidden category, do not update the visibleold flag - we want to get
+ // to previous state if somebody unhides the category.
$course->visible = 0;
}
$DB->update_record('course', $course);
- add_to_log($course->id, "course", "move", "edit.php?id=$course->id", $course->id);
- $context = context_course::instance($course->id);
+ // Store the context.
+ $context = context_course::instance($course->id);
+
+ // Update the course object we are passing to the event.
+ $dbcourse->category = $course->category;
+ $dbcourse->sortorder = $course->sortorder;
+
+ // Trigger a course updated event.
+ $event = \core\event\course_updated::create(array(
+ 'objectid' => $course->id,
+ 'context' => $context,
+ 'other' => array('shortname' => $dbcourse->shortname,
+ 'fullname' => $dbcourse->fullname)
+ ));
+ $event->add_record_snapshot('course', $dbcourse);
+ $event->set_legacy_logdata(array($course->id, 'course', 'move', 'edit.php?id=' . $course->id, $course->id));
+ $event->trigger();
+
$context->update_moved($newparent);
}
}
@@ -2229,7 +2246,7 @@ function course_overviewfiles_options($course) {
* @return object new course instance
*/
function create_course($data, $editoroptions = NULL) {
- global $CFG, $DB;
+ global $DB;
//check the categoryid - must be given for all new courses
$category = $DB->get_record('course_categories', array('id'=>$data->category), '*', MUST_EXIST);
@@ -2304,10 +2321,15 @@ function create_course($data, $editoroptions = NULL) {
// set up enrolments
enrol_course_updated(true, $course, $data);
- add_to_log(SITEID, 'course', 'new', 'view.php?id='.$course->id, $data->fullname.' (ID '.$course->id.')');
-
- // Trigger events
- events_trigger('course_created', $course);
+ // Trigger a course created event.
+ $event = \core\event\course_created::create(array(
+ 'objectid' => $course->id,
+ 'context' => context_course::instance($course->id),
+ 'other' => array('shortname' => $course->shortname,
+ 'fullname' => $course->fullname)
+ ));
+ $event->add_record_snapshot('course', $course);
+ $event->trigger();
return $course;
}
@@ -2323,7 +2345,7 @@ function create_course($data, $editoroptions = NULL) {
* @return void
*/
function update_course($data, $editoroptions = NULL) {
- global $CFG, $DB;
+ global $DB;
$data->timemodified = time();
@@ -2393,10 +2415,16 @@ function update_course($data, $editoroptions = NULL) {
// update enrol settings
enrol_course_updated(false, $course, $data);
- add_to_log($course->id, "course", "update", "edit.php?id=$course->id", $course->id);
-
- // Trigger events
- events_trigger('course_updated', $course);
+ // Trigger a course updated event.
+ $event = \core\event\course_updated::create(array(
+ 'objectid' => $course->id,
+ 'context' => $context,
+ 'other' => array('shortname' => $course->shortname,
+ 'fullname' => $course->fullname)
+ ));
+ $event->add_record_snapshot('course', $course);
+ $event->set_legacy_logdata(array($course->id, 'course', 'update', 'edit.php?id=' . $course->id, $course->id));
+ $event->trigger();
if ($oldcourse->format !== $course->format) {
// Remove all options stored for the previous format
@@ -2898,7 +2926,7 @@ function course_ajax_enabled($course) {
* @return bool
*/
function include_course_ajax($course, $usedmodules = array(), $enabledmodules = null, $config = null) {
- global $PAGE, $SITE;
+ global $CFG, $PAGE, $SITE;
// Ensure that ajax should be included
if (!course_ajax_enabled($course)) {
@@ -2977,6 +3005,9 @@ function include_course_ajax($course, $usedmodules = array(), $enabledmodules =
'markedthistopic',
'move',
'movesection',
+ 'movecontent',
+ 'tocontent',
+ 'emptydragdropregion'
), 'moodle');
// Include format-specific strings
@@ -2993,6 +3024,7 @@ function include_course_ajax($course, $usedmodules = array(), $enabledmodules =
}
// Load drag and drop upload AJAX.
+ require_once($CFG->dirroot.'/course/dnduploadlib.php');
dndupload_add_to_course($course, $enabledmodules);
return true;
diff --git a/course/loginas.php b/course/loginas.php
index 0c1ec266ae9..97f7d2a81ef 100644
--- a/course/loginas.php
+++ b/course/loginas.php
@@ -1,5 +1,5 @@
$id));
$PAGE->set_url($url);
-/// Reset user back to their real self if needed, for security reasons you need to log out and log in again
+// Reset user back to their real self if needed, for security reasons you need to log out and log in again.
if (session_is_loggedinas()) {
require_sesskey();
require_logout();
@@ -29,15 +29,13 @@ if ($redirect) {
redirect(get_login_url());
}
-///-------------------------------------
-/// We are trying to log in as this user in the first place
-
-$userid = required_param('user', PARAM_INT); // login as this user
+// Try log in as this user.
+$userid = required_param('user', PARAM_INT);
require_sesskey();
$course = $DB->get_record('course', array('id'=>$id), '*', MUST_EXIST);
-/// User must be logged in
+// User must be logged in.
$systemcontext = context_system::instance();
$coursecontext = context_course::instance($course->id);
@@ -62,13 +60,10 @@ if (has_capability('moodle/user:loginas', $systemcontext)) {
$context = $coursecontext;
}
-/// Login as this user and return to course home page.
-$oldfullname = fullname($USER, true);
+// Login as this user and return to course home page.
session_loginas($userid, $context);
$newfullname = fullname($USER, true);
-add_to_log($course->id, "course", "loginas", "../user/view.php?id=$course->id&user=$userid", "$oldfullname -> $newfullname");
-
$strloginas = get_string('loginas');
$strloggedinas = get_string('loggedinas', '', $newfullname);
diff --git a/course/manage.php b/course/manage.php
index d4a905455cd..4888c0a4960 100644
--- a/course/manage.php
+++ b/course/manage.php
@@ -249,7 +249,22 @@ if ((!empty($hide) or !empty($show)) && confirm_sesskey()) {
$params = array('id' => $course->id, 'visible' => $visible, 'visibleold' => $visible, 'timemodified' => time());
$DB->update_record('course', $params);
cache_helper::purge_by_event('changesincourse');
- add_to_log($course->id, "course", ($visible ? 'show' : 'hide'), "edit.php?id=$course->id", $course->id);
+
+ // Update the course object we pass to the event class.
+ $course->visible = $params['visible'];
+ $course->visibleold = $params['visibleold'];
+ $course->timemodified = $params['timemodified'];
+
+ // Trigger a course updated event.
+ $event = \core\event\course_updated::create(array(
+ 'objectid' => $course->id,
+ 'context' => $coursecontext,
+ 'other' => array('shortname' => $course->shortname,
+ 'fullname' => $course->fullname)
+ ));
+ $event->add_record_snapshot('course', $course);
+ $event->set_legacy_logdata(array($course->id, 'course', ($visible ? 'show' : 'hide'), 'edit.php?id=' . $course->id, $course->id));
+ $event->trigger();
}
if ((!empty($moveup) or !empty($movedown)) && confirm_sesskey()) {
@@ -277,7 +292,20 @@ if ((!empty($moveup) or !empty($movedown)) && confirm_sesskey()) {
$DB->set_field('course', 'sortorder', $swapcourse->sortorder, array('id' => $movecourse->id));
$DB->set_field('course', 'sortorder', $movecourse->sortorder, array('id' => $swapcourse->id));
cache_helper::purge_by_event('changesincourse');
- add_to_log($movecourse->id, "course", "move", "edit.php?id=$movecourse->id", $movecourse->id);
+
+ // Update $movecourse's sortorder.
+ $movecourse->sortorder = $swapcourse->sortorder;
+
+ // Trigger a course updated event.
+ $event = \core\event\course_updated::create(array(
+ 'objectid' => $movecourse->id,
+ 'context' => context_course::instance($movecourse->id),
+ 'other' => array('shortname' => $movecourse->shortname,
+ 'fullname' => $movecourse->fullname)
+ ));
+ $event->add_record_snapshot('course', $movecourse);
+ $event->set_legacy_logdata(array($movecourse->id, 'course', 'move', 'edit.php?id=' . $movecourse->id, $movecourse->id));
+ $event->trigger();
}
}
diff --git a/course/tests/courselib_test.php b/course/tests/courselib_test.php
index b805289ca06..01c4bfc697c 100644
--- a/course/tests/courselib_test.php
+++ b/course/tests/courselib_test.php
@@ -1334,4 +1334,370 @@ class core_course_courselib_testcase extends advanced_testcase {
$eventcount = $DB->count_records('event', array('instance' => $assign->id, 'modulename' => 'assign'));
$this->assertEmpty($eventcount);
}
+
+ /**
+ * Test that triggering a course_created event works as expected.
+ */
+ public function test_course_created_event() {
+ $this->resetAfterTest();
+
+ // Catch the events.
+ $sink = $this->redirectEvents();
+
+ // Create the course.
+ $course = $this->getDataGenerator()->create_course();
+
+ // Capture the event.
+ $events = $sink->get_events();
+ $sink->close();
+
+ // Validate the event.
+ $event = $events[0];
+ $this->assertInstanceOf('\core\event\course_created', $event);
+ $this->assertEquals('course', $event->objecttable);
+ $this->assertEquals($course->id, $event->objectid);
+ $this->assertEquals(context_course::instance($course->id)->id, $event->contextid);
+ $this->assertEquals($course, $event->get_record_snapshot('course', $course->id));
+ $this->assertEquals('course_created', $event->get_legacy_eventname());
+ $this->assertEventLegacyData($course, $event);
+ $expectedlog = array(SITEID, 'course', 'new', 'view.php?id=' . $course->id, $course->fullname . ' (ID ' . $course->id . ')');
+ $this->assertEventLegacyLogData($expectedlog, $event);
+ }
+
+ /**
+ * Test that triggering a course_updated event works as expected.
+ */
+ public function test_course_updated_event() {
+ global $DB;
+
+ $this->resetAfterTest();
+
+ // Create a course.
+ $course = $this->getDataGenerator()->create_course();
+
+ // Create a category we are going to move this course to.
+ $category = $this->getDataGenerator()->create_category();
+
+ // Catch the update events.
+ $sink = $this->redirectEvents();
+
+ // Keep track of the old sortorder.
+ $sortorder = $course->sortorder;
+
+ // Call update_course which will trigger a course_updated event.
+ update_course($course);
+
+ // Return the updated course information from the DB.
+ $updatedcourse = $DB->get_record('course', array('id' => $course->id), '*', MUST_EXIST);
+
+ // Now move the course to the category, this will also trigger an event.
+ move_courses(array($course->id), $category->id);
+
+ // Return the moved course information from the DB.
+ $movedcourse = $DB->get_record('course', array('id' => $course->id), '*', MUST_EXIST);
+
+ // Now we want to set the sortorder back to what it was before fix_course_sortorder() was called. The reason for
+ // this is because update_course() and move_courses() call fix_course_sortorder() which alters the sort order in
+ // the DB, but it does not set the value of the sortorder for the course object passed to the event.
+ $updatedcourse->sortorder = $sortorder;
+ $movedcourse->sortorder = $category->sortorder + MAX_COURSES_IN_CATEGORY - 1;
+
+ // Capture the events.
+ $events = $sink->get_events();
+ $sink->close();
+
+ // Validate the events.
+ $event = $events[0];
+ $this->assertInstanceOf('\core\event\course_updated', $event);
+ $this->assertEquals('course', $event->objecttable);
+ $this->assertEquals($updatedcourse->id, $event->objectid);
+ $this->assertEquals(context_course::instance($updatedcourse->id)->id, $event->contextid);
+ $this->assertEquals($updatedcourse, $event->get_record_snapshot('course', $updatedcourse->id));
+ $this->assertEquals('course_updated', $event->get_legacy_eventname());
+ $this->assertEventLegacyData($updatedcourse, $event);
+ $expectedlog = array($updatedcourse->id, 'course', 'update', 'edit.php?id=' . $course->id, $course->id);
+ $this->assertEventLegacyLogData($expectedlog, $event);
+
+ $event = $events[1];
+ $this->assertInstanceOf('\core\event\course_updated', $event);
+ $this->assertEquals('course', $event->objecttable);
+ $this->assertEquals($movedcourse->id, $event->objectid);
+ $this->assertEquals(context_course::instance($movedcourse->id)->id, $event->contextid);
+ $this->assertEquals($movedcourse, $event->get_record_snapshot('course', $movedcourse->id));
+ $this->assertEquals('course_updated', $event->get_legacy_eventname());
+ $this->assertEventLegacyData($movedcourse, $event);
+ $expectedlog = array($movedcourse->id, 'course', 'move', 'edit.php?id=' . $movedcourse->id, $movedcourse->id);
+ $this->assertEventLegacyLogData($expectedlog, $event);
+ }
+
+ /**
+ * Test that triggering a course_deleted event works as expected.
+ */
+ public function test_course_deleted_event() {
+ $this->resetAfterTest();
+
+ // Create the course.
+ $course = $this->getDataGenerator()->create_course();
+
+ // Save the course context before we delete the course.
+ $coursecontext = context_course::instance($course->id);
+
+ // Catch the update event.
+ $sink = $this->redirectEvents();
+
+ // Call delete_course() which will trigger the course_deleted event and the course_content_deleted
+ // event. This function prints out data to the screen, which we do not want during a PHPUnit test,
+ // so use ob_start and ob_end_clean to prevent this.
+ ob_start();
+ delete_course($course);
+ ob_end_clean();
+
+ // Capture the event.
+ $events = $sink->get_events();
+ $sink->close();
+
+ // Validate the event.
+ $event = $events[1];
+ $this->assertInstanceOf('\core\event\course_deleted', $event);
+ $this->assertEquals('course', $event->objecttable);
+ $this->assertEquals($course->id, $event->objectid);
+ $this->assertEquals($coursecontext->id, $event->contextid);
+ $this->assertEquals($course, $event->get_record_snapshot('course', $course->id));
+ $this->assertEquals('course_deleted', $event->get_legacy_eventname());
+ // The legacy data also passed the context in the course object.
+ $course->context = $coursecontext;
+ $this->assertEventLegacyData($course, $event);
+ $expectedlog = array(SITEID, 'course', 'delete', 'view.php?id=' . $course->id, $course->fullname . '(ID ' . $course->id . ')');
+ $this->assertEventLegacyLogData($expectedlog, $event);
+ }
+
+ /**
+ * Test that triggering a course_content_deleted event works as expected.
+ */
+ public function test_course_content_deleted_event() {
+ global $DB;
+
+ $this->resetAfterTest();
+
+ // Create the course.
+ $course = $this->getDataGenerator()->create_course();
+
+ // Get the course from the DB. The data generator adds some extra properties, such as
+ // numsections, to the course object which will fail the assertions later on.
+ $course = $DB->get_record('course', array('id' => $course->id), '*', MUST_EXIST);
+
+ // Save the course context before we delete the course.
+ $coursecontext = context_course::instance($course->id);
+
+ // Catch the update event.
+ $sink = $this->redirectEvents();
+
+ // Call remove_course_contents() which will trigger the course_content_deleted event.
+ // This function prints out data to the screen, which we do not want during a PHPUnit
+ // test, so use ob_start and ob_end_clean to prevent this.
+ ob_start();
+ remove_course_contents($course->id);
+ ob_end_clean();
+
+ // Capture the event.
+ $events = $sink->get_events();
+ $sink->close();
+
+ // Validate the event.
+ $event = $events[0];
+ $this->assertInstanceOf('\core\event\course_content_deleted', $event);
+ $this->assertEquals('course', $event->objecttable);
+ $this->assertEquals($course->id, $event->objectid);
+ $this->assertEquals($coursecontext->id, $event->contextid);
+ $this->assertEquals($course, $event->get_record_snapshot('course', $course->id));
+ $this->assertEquals('course_content_removed', $event->get_legacy_eventname());
+ // The legacy data also passed the context and options in the course object.
+ $course->context = $coursecontext;
+ $course->options = array();
+ $this->assertEventLegacyData($course, $event);
+ }
+
+ /**
+ * Test that triggering a course_category_deleted event works as expected.
+ */
+ public function test_course_category_deleted_event() {
+ $this->resetAfterTest();
+
+ // Create a category.
+ $category = $this->getDataGenerator()->create_category();
+
+ // Save the context before it is deleted.
+ $categorycontext = context_coursecat::instance($category->id);
+
+ // Catch the update event.
+ $sink = $this->redirectEvents();
+
+ // Delete the category.
+ $category->delete_full();
+
+ // Capture the event.
+ $events = $sink->get_events();
+ $sink->close();
+
+ // Validate the event.
+ $event = $events[0];
+ $this->assertInstanceOf('\core\event\course_category_deleted', $event);
+ $this->assertEquals('course_categories', $event->objecttable);
+ $this->assertEquals($category->id, $event->objectid);
+ $this->assertEquals($categorycontext->id, $event->contextid);
+ $this->assertEquals('course_category_deleted', $event->get_legacy_eventname());
+ $this->assertEventLegacyData($category, $event);
+ $expectedlog = array(SITEID, 'category', 'delete', 'index.php', $category->name . '(ID ' . $category->id . ')');
+ $this->assertEventLegacyLogData($expectedlog, $event);
+
+ // Create two categories.
+ $category = $this->getDataGenerator()->create_category();
+ $category2 = $this->getDataGenerator()->create_category();
+
+ // Save the context before it is moved and then deleted.
+ $category2context = context_coursecat::instance($category2->id);
+
+ // Catch the update event.
+ $sink = $this->redirectEvents();
+
+ // Move the category.
+ $category2->delete_move($category->id);
+
+ // Capture the event.
+ $events = $sink->get_events();
+ $sink->close();
+
+ // Validate the event.
+ $event = $events[0];
+ $this->assertInstanceOf('\core\event\course_category_deleted', $event);
+ $this->assertEquals('course_categories', $event->objecttable);
+ $this->assertEquals($category2->id, $event->objectid);
+ $this->assertEquals($category2context->id, $event->contextid);
+ $this->assertEquals('course_category_deleted', $event->get_legacy_eventname());
+ $this->assertEventLegacyData($category2, $event);
+ $expectedlog = array(SITEID, 'category', 'delete', 'index.php', $category2->name . '(ID ' . $category2->id . ')');
+ $this->assertEventLegacyLogData($expectedlog, $event);
+ }
+
+ /**
+ * Test that triggering a course_restored event works as expected.
+ */
+ public function test_course_restored_event() {
+ global $CFG;
+
+ // Get the necessary files to perform backup and restore.
+ require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
+ require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
+
+ $this->resetAfterTest();
+
+ // Set to admin user.
+ $this->setAdminUser();
+
+ // The user id is going to be 2 since we are the admin user.
+ $userid = 2;
+
+ // Create a course.
+ $course = $this->getDataGenerator()->create_course();
+
+ // Create backup file and save it to the backup location.
+ $bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE,
+ backup::INTERACTIVE_NO, backup::MODE_GENERAL, $userid);
+ $bc->execute_plan();
+ $results = $bc->get_results();
+ $file = $results['backup_destination'];
+ $fp = get_file_packer();
+ $filepath = $CFG->dataroot . '/temp/backup/test-restore-course-event';
+ $file->extract_to_pathname($fp, $filepath);
+ $bc->destroy();
+ unset($bc);
+
+ // Now we want to catch the restore course event.
+ $sink = $this->redirectEvents();
+
+ // Now restore the course to trigger the event.
+ $rc = new restore_controller('test-restore-course-event', $course->id, backup::INTERACTIVE_NO,
+ backup::MODE_GENERAL, $userid, backup::TARGET_NEW_COURSE);
+ $rc->execute_precheck();
+ $rc->execute_plan();
+
+ // Capture the event.
+ $events = $sink->get_events();
+ $sink->close();
+
+ // Validate the event.
+ $event = $events[0];
+ $this->assertInstanceOf('\core\event\course_restored', $event);
+ $this->assertEquals('course', $event->objecttable);
+ $this->assertEquals($rc->get_courseid(), $event->objectid);
+ $this->assertEquals(context_course::instance($rc->get_courseid())->id, $event->contextid);
+ $this->assertEquals('course_restored', $event->get_legacy_eventname());
+ $legacydata = (object) array(
+ 'courseid' => $rc->get_courseid(),
+ 'userid' => $rc->get_userid(),
+ 'type' => $rc->get_type(),
+ 'target' => $rc->get_target(),
+ 'mode' => $rc->get_mode(),
+ 'operation' => $rc->get_operation(),
+ 'samesite' => $rc->is_samesite()
+ );
+ $this->assertEventLegacyData($legacydata, $event);
+
+ // Destroy the resource controller since we are done using it.
+ $rc->destroy();
+ unset($rc);
+
+ // Clear the time limit, otherwise PHPUnit complains.
+ set_time_limit(0);
+ }
+
+ /**
+ * Test that triggering a course_section_updated event works as expected.
+ */
+ public function test_course_section_updated_event() {
+ global $DB;
+
+ $this->resetAfterTest();
+
+ // Create the course with sections.
+ $course = $this->getDataGenerator()->create_course(array('numsections' => 10), array('createsections' => true));
+ $sections = $DB->get_records('course_sections', array('course' => $course->id));
+
+ $coursecontext = context_course::instance($course->id);
+
+ $section = array_pop($sections);
+ $section->name = 'Test section';
+ $section->summary = 'Test section summary';
+ $DB->update_record('course_sections', $section);
+
+ // Trigger an event for course section update.
+ $event = \core\event\course_section_updated::create(
+ array(
+ 'objectid' => $section->id,
+ 'courseid' => $course->id,
+ 'context' => context_course::instance($course->id)
+ )
+ );
+ $event->add_record_snapshot('course_sections', $section);
+ // Trigger and catch event.
+ $sink = $this->redirectEvents();
+ $event->trigger();
+ $events = $sink->get_events();
+ $sink->close();
+
+ // Validate the event.
+ $event = $events[0];
+ $this->assertInstanceOf('\core\event\course_section_updated', $event);
+ $this->assertEquals('course_sections', $event->objecttable);
+ $this->assertEquals($section->id, $event->objectid);
+ $this->assertEquals($course->id, $event->courseid);
+ $this->assertEquals($coursecontext->id, $event->contextid);
+ $expecteddesc = 'Course ' . $event->courseid . ' section ' . $event->other['sectionnum'] . ' updated by user ' . $event->userid;
+ $this->assertEquals($expecteddesc, $event->get_description());
+ $this->assertEquals($section, $event->get_record_snapshot('course_sections', $event->objectid));
+ $id = $section->id;
+ $sectionnum = $section->section;
+ $expectedlegacydata = array($course->id, "course", "editsection", 'editsection.php?id=' . $id, $sectionnum);
+ $this->assertEventLegacyLogData($expectedlegacydata, $event);
+ }
}
diff --git a/course/yui/dragdrop/dragdrop.js b/course/yui/dragdrop/dragdrop.js
index 0145f0fb8f5..fc5f88d2e40 100644
--- a/course/yui/dragdrop/dragdrop.js
+++ b/course/yui/dragdrop/dragdrop.js
@@ -295,6 +295,7 @@ YUI.add('moodle-course-dragdrop', function(Y) {
resources.addClass(CSS.SECTION);
sectionnode.one('.'+CSS.CONTENT+' div.'+CSS.SUMMARY).insert(resources, 'after');
}
+ resources.setAttribute('data-draggroups', this.groups.join(' '));
// Define empty ul as droptarget, so that item could be moved to empty list
var tar = new Y.DD.Drop({
node: resources,
diff --git a/enrol/flatfile/lib.php b/enrol/flatfile/lib.php
index 4add14e4b25..e0117b97feb 100644
--- a/enrol/flatfile/lib.php
+++ b/enrol/flatfile/lib.php
@@ -311,8 +311,8 @@ class enrol_flatfile_plugin extends enrol_plugin {
}
$roleid = $rolemap[$fields[1]];
- if (empty($fields[2]) or !$user = $DB->get_record("user", array("idnumber"=>$fields[2]))) {
- $trace->output("Unknown user idnumber in field 3 - ignoring line $line", 1);
+ if (empty($fields[2]) or !$user = $DB->get_record("user", array("idnumber"=>$fields[2], 'deleted'=>0))) {
+ $trace->output("Unknown user idnumber or deleted user in field 3 - ignoring line $line", 1);
continue;
}
diff --git a/enrol/ldap/cli/sync.php b/enrol/ldap/cli/sync.php
index e43b12c1665..c1bff98536a 100644
--- a/enrol/ldap/cli/sync.php
+++ b/enrol/ldap/cli/sync.php
@@ -46,7 +46,7 @@ require(__DIR__.'/../../../config.php');
require_once("$CFG->libdir/clilib.php");
// Ensure errors are well explained.
-$CFG->debug = DEBUG_DEVELOPER;
+set_debugging(DEBUG_DEVELOPER, true);
if (!enrol_is_enabled('ldap')) {
cli_error(get_string('pluginnotenabled', 'enrol_ldap'), 2);
diff --git a/enrol/tests/enrollib_test.php b/enrol/tests/enrollib_test.php
index 250fcd8ba92..cba40a8c338 100644
--- a/enrol/tests/enrollib_test.php
+++ b/enrol/tests/enrollib_test.php
@@ -250,4 +250,33 @@ class core_enrollib_testcase extends advanced_testcase {
$this->assertTrue(enrol_user_sees_own_courses());
$this->assertEquals($reads, $DB->perf_get_reads());
}
+
+ public function test_enrol_get_shared_courses() {
+ $this->resetAfterTest();
+
+ $user1 = $this->getDataGenerator()->create_user();
+ $user2 = $this->getDataGenerator()->create_user();
+ $user3 = $this->getDataGenerator()->create_user();
+
+ $course1 = $this->getDataGenerator()->create_course();
+ $this->getDataGenerator()->enrol_user($user1->id, $course1->id);
+ $this->getDataGenerator()->enrol_user($user2->id, $course1->id);
+
+ $course2 = $this->getDataGenerator()->create_course();
+ $this->getDataGenerator()->enrol_user($user1->id, $course2->id);
+
+ // Test that user1 and user2 have courses in common.
+ $this->assertTrue(enrol_get_shared_courses($user1, $user2, false, true));
+ // Test that user1 and user3 have no courses in common.
+ $this->assertFalse(enrol_get_shared_courses($user1, $user3, false, true));
+
+ // Test retrieving the courses in common.
+ $sharedcourses = enrol_get_shared_courses($user1, $user2, true);
+
+ // Only should be one shared course.
+ $this->assertCount(1, $sharedcourses);
+ $sharedcourse = array_shift($sharedcourses);
+ // It should be course 1.
+ $this->assertEquals($sharedcourse->id, $course1->id);
+ }
}
diff --git a/enrol/yui/rolemanager/assets/skins/sam/rolemanager.css b/enrol/yui/rolemanager/assets/skins/sam/rolemanager.css
index 423ea149b1a..af37580fbc9 100644
--- a/enrol/yui/rolemanager/assets/skins/sam/rolemanager.css
+++ b/enrol/yui/rolemanager/assets/skins/sam/rolemanager.css
@@ -5,4 +5,5 @@
.enrolpanel .container .header h2 {font-size:90%;text-align:center;margin:5px;}
.enrolpanel .container .header .close {width:25px;height:15px;position:absolute;top:5px;right:1em;cursor:pointer;background:url("sprite.png") no-repeat scroll 0 0 transparent;}
.enrolpanel .container .content {}
-.enrolpanel .container .content input {margin:5px;font-size:10px;}
\ No newline at end of file
+.enrolpanel .container .content input {margin:5px;font-size:10px;}
+.enrolpanel.roleassign.visible .container {width:auto;}
diff --git a/enrol/yui/rolemanager/rolemanager.js b/enrol/yui/rolemanager/rolemanager.js
index 90808888336..6c82cc36bcc 100644
--- a/enrol/yui/rolemanager/rolemanager.js
+++ b/enrol/yui/rolemanager/rolemanager.js
@@ -381,7 +381,11 @@ YUI.add('moodle-enrol-rolemanager', function(Y) {
var roles = this.user.get(CONTAINER).one('.col_role .roles');
var x = roles.getX() + 10;
var y = roles.getY() + this.user.get(CONTAINER).get('offsetHeight') - 10;
- this.get('elementNode').setStyle('left', x).setStyle('top', y);
+ if ( Y.one(document.body).hasClass('dir-rtl') ) {
+ this.get('elementNode').setStyle('right', x - 20).setStyle('top', y);
+ } else {
+ this.get('elementNode').setStyle('left', x).setStyle('top', y);
+ }
this.get('elementNode').addClass('visible');
this.escCloseEvent = Y.on('key', this.hide, document.body, 'down:27', this);
this.displayed = true;
diff --git a/files/externallib.php b/files/externallib.php
index e086c6ea5e3..e97478dcbc8 100644
--- a/files/externallib.php
+++ b/files/externallib.php
@@ -47,13 +47,16 @@ class core_files_external extends external_api {
public static function get_files_parameters() {
return new external_function_parameters(
array(
- 'contextid' => new external_value(PARAM_INT, 'context id'),
- 'component' => new external_value(PARAM_TEXT, 'component'),
- 'filearea' => new external_value(PARAM_TEXT, 'file area'),
- 'itemid' => new external_value(PARAM_INT, 'associated id'),
- 'filepath' => new external_value(PARAM_PATH, 'file path'),
- 'filename' => new external_value(PARAM_FILE, 'file name'),
- 'modified' => new external_value(PARAM_INT, 'timestamp to return files changed after this time.', VALUE_DEFAULT, null)
+ 'contextid' => new external_value(PARAM_INT, 'context id Set to -1 to use contextlevel and instanceid.'),
+ 'component' => new external_value(PARAM_TEXT, 'component'),
+ 'filearea' => new external_value(PARAM_TEXT, 'file area'),
+ 'itemid' => new external_value(PARAM_INT, 'associated id'),
+ 'filepath' => new external_value(PARAM_PATH, 'file path'),
+ 'filename' => new external_value(PARAM_FILE, 'file name'),
+ 'modified' => new external_value(PARAM_INT, 'timestamp to return files changed after this time.', VALUE_DEFAULT, null),
+ 'contextlevel' => new external_value(PARAM_ALPHA, 'The context level for the file location.', VALUE_DEFAULT, null),
+ 'instanceid' => new external_value(PARAM_INT, 'The instance id for where the file is located.', VALUE_DEFAULT, null)
+
)
);
}
@@ -68,22 +71,41 @@ class core_files_external extends external_api {
* @param string $filepath file path
* @param string $filename file name
* @param int $modified timestamp to return files changed after this time.
+ * @param string $contextlevel The context level for the file location.
+ * @param int $instanceid The instance id for where the file is located.
* @return array
* @since Moodle 2.2
*/
- public static function get_files($contextid, $component, $filearea, $itemid, $filepath, $filename, $modified = null) {
- global $CFG, $USER, $OUTPUT;
- $fileinfo = self::validate_parameters(self::get_files_parameters(), array(
- 'contextid'=>$contextid, 'component'=>$component, 'filearea'=>$filearea,
- 'itemid'=>$itemid, 'filepath'=>$filepath, 'filename'=>$filename, 'modified'=>$modified));
+ public static function get_files($contextid, $component, $filearea, $itemid, $filepath, $filename, $modified = null,
+ $contextlevel = null, $instanceid = null) {
+
+ $parameters = array(
+ 'contextid' => $contextid,
+ 'component' => $component,
+ 'filearea' => $filearea,
+ 'itemid' => $itemid,
+ 'filepath' => $filepath,
+ 'filename' => $filename,
+ 'modified' => $modified,
+ 'contextlevel' => $contextlevel,
+ 'instanceid' => $instanceid);
+ $fileinfo = self::validate_parameters(self::get_files_parameters(), $parameters);
$browser = get_file_browser();
- if (empty($fileinfo['contextid'])) {
- $context = context_system::instance();
+ // We need to preserve backwards compatibility. Zero will use the system context and minus one will
+ // use the addtional parameters to determine the context.
+ // TODO MDL-40489 get_context_from_params should handle this logic.
+ if ($fileinfo['contextid'] == 0) {
+ $context = context_system::instance();
} else {
- $context = context::instance_by_id($fileinfo['contextid']);
+ if ($fileinfo['contextid'] == -1) {
+ $fileinfo['contextid'] = null;
+ }
+ $context = self::get_context_from_params($fileinfo);
}
+ self::validate_context($context);
+
if (empty($fileinfo['component'])) {
$fileinfo['component'] = null;
}
@@ -104,6 +126,7 @@ class core_files_external extends external_api {
$return['parents'] = array();
$return['files'] = array();
$list = array();
+
if ($file = $browser->get_file_info(
$context, $fileinfo['component'], $fileinfo['filearea'], $fileinfo['itemid'],
$fileinfo['filepath'], $fileinfo['filename'])) {
diff --git a/files/tests/externallib_test.php b/files/tests/externallib_test.php
index 7f6de7d586d..4e21d91335f 100644
--- a/files/tests/externallib_test.php
+++ b/files/tests/externallib_test.php
@@ -175,4 +175,121 @@ class core_files_externallib_testcase extends advanced_testcase {
$file = $browser->get_file_info($context, $component, $filearea, $itemid, $filepath, $filename);
$this->assertNotEmpty($file);
}
+
+ public function test_get_files() {
+ global $USER, $DB;
+
+ $this->resetAfterTest();
+
+ $this->setAdminUser();
+ $USER->email = 'test@moodle.com';
+
+ $course = $this->getDataGenerator()->create_course();
+ $record = new stdClass();
+ $record->course = $course->id;
+ $record->name = "Mod data upload test";
+
+ $record->intro = "Some intro of some sort";
+
+ $module = $this->getDataGenerator()->create_module('data', $record);
+
+ $field = data_get_field_new('file', $module);
+
+ $fielddetail = new stdClass();
+ $fielddetail->d = $module->id;
+ $fielddetail->mode = 'add';
+ $fielddetail->type = 'file';
+ $fielddetail->sesskey = sesskey();
+ $fielddetail->name = 'Upload file';
+ $fielddetail->description = 'Some description';
+ $fielddetail->param3 = '0';
+
+ $field->define_field($fielddetail);
+ $field->insert_field();
+ $recordid = data_add_record($module);
+
+ $timemodified = $DB->get_field('data_records', 'timemodified', array('id' => $recordid));
+
+ $datacontent = array();
+ $datacontent['fieldid'] = $field->field->id;
+ $datacontent['recordid'] = $recordid;
+ $datacontent['content'] = 'Simple4.txt';
+
+ $contentid = $DB->insert_record('data_content', $datacontent);
+
+ $context = context_module::instance($module->id);
+ $usercontext = context_user::instance($USER->id);
+ $component = 'mod_data';
+ $filearea = 'content';
+ $itemid = $contentid;
+ $filename = $datacontent['content'];
+ $filecontent = base64_encode("Let us create a nice simple file.");
+
+ $filerecord = array();
+ $filerecord['contextid'] = $context->id;
+ $filerecord['component'] = $component;
+ $filerecord['filearea'] = $filearea;
+ $filerecord['itemid'] = $itemid;
+ $filerecord['filepath'] = '/';
+ $filerecord['filename'] = $filename;
+
+ $fs = get_file_storage();
+ $file = $fs->create_file_from_string($filerecord, $filecontent);
+
+ $filename = '';
+ $testfilelisting = core_files_external::get_files($context->id, $component, $filearea, $itemid, '/', $filename);
+
+ $testdata = array();
+ $testdata['parents'] = array();
+ $testdata['parents']['0'] = array('contextid' => 1,
+ 'component' => null,
+ 'filearea' => null,
+ 'itemid' => null,
+ 'filepath' => null,
+ 'filename' => 'System');
+ $testdata['parents']['1'] = array('contextid' => 3,
+ 'component' => null,
+ 'filearea' => null,
+ 'itemid' => null,
+ 'filepath' => null,
+ 'filename' => 'Miscellaneous');
+ $testdata['parents']['2'] = array('contextid' => 15,
+ 'component' => null,
+ 'filearea' => null,
+ 'itemid' => null,
+ 'filepath' => null,
+ 'filename' => 'Test course 1');
+ $testdata['parents']['3'] = array('contextid' => 20,
+ 'component' => null,
+ 'filearea' => null,
+ 'itemid' => null,
+ 'filepath' => null,
+ 'filename' => 'Mod data upload test (Database)');
+ $testdata['parents']['4'] = array('contextid' => 20,
+ 'component' => 'mod_data',
+ 'filearea' => 'content',
+ 'itemid' => null,
+ 'filepath' => null,
+ 'filename' => 'Fields');
+ $testdata['files'] = array();
+ $testdata['files']['0'] = array('contextid' => 20,
+ 'component' => 'mod_data',
+ 'filearea' => 'content',
+ 'itemid' => 1,
+ 'filepath' => '/',
+ 'filename' => 'Simple4.txt',
+ 'url' => 'http://www.example.com/moodle/pluginfile.php/20/mod_data/content/1/Simple4.txt',
+ 'isdir' => null,
+ 'timemodified' => $timemodified);
+
+ $this->assertEquals($testfilelisting, $testdata);
+
+ // Try again but without the context.
+ $nocontext = -1;
+ $modified = 0;
+ $contextlevel = 'module';
+ $instanceid = $module->id;
+ $testfilelisting = core_files_external::get_files($nocontext, $component, $filearea, $itemid, '/', $filename, $modified, $contextlevel, $instanceid);
+ $this->assertEquals($testfilelisting, $testdata);
+ }
}
diff --git a/install.php b/install.php
index 7b5fa239677..899ebd35e38 100644
--- a/install.php
+++ b/install.php
@@ -181,6 +181,9 @@ $CFG->umaskpermissions = (($CFG->directorypermissions & 0777) ^ 0777);
$CFG->running_installer = true;
$CFG->early_install_lang = true;
$CFG->ostype = (stristr(PHP_OS, 'win') && !stristr(PHP_OS, 'darwin')) ? 'WINDOWS' : 'UNIX';
+$CFG->debug = (E_ALL | E_STRICT);
+$CFG->debugdisplay = true;
+$CFG->debugdeveloper = true;
// Require all needed libs
require_once($CFG->libdir.'/setuplib.php');
diff --git a/lang/en/auth.php b/lang/en/auth.php
index 8533af90130..ac2377099b0 100644
--- a/lang/en/auth.php
+++ b/lang/en/auth.php
@@ -82,6 +82,7 @@ $string['errorminpasswordnonalphanum'] = 'Passwords must have at least {$a} non-
$string['errorminpasswordupper'] = 'Passwords must have at least {$a} upper case letter(s).';
$string['errorpasswordupdate'] = 'Error updating password, password not changed';
$string['event_user_loggedin'] = 'User has logged in';
+$string['eventuserloggedinas'] = 'User logged in as another user';
$string['forcechangepassword'] = 'Force change password';
$string['forcechangepasswordfirst_help'] = 'Force users to change password on their first login to Moodle.';
$string['forcechangepassword_help'] = 'Force users to change password on their next login to Moodle.';
diff --git a/lang/en/cohort.php b/lang/en/cohort.php
index ec04a5c9514..44e659929ad 100644
--- a/lang/en/cohort.php
+++ b/lang/en/cohort.php
@@ -45,6 +45,11 @@ $string['delconfirm'] = 'Do you really want to delete cohort \'{$a}\'?';
$string['description'] = 'Description';
$string['duplicateidnumber'] = 'Cohort with the same ID number already exists';
$string['editcohort'] = 'Edit cohort';
+$string['event_cohort_created'] = 'Cohort created';
+$string['event_cohort_deleted'] = 'Cohort deleted';
+$string['event_cohort_member_added'] = 'User added to a cohort';
+$string['event_cohort_member_removed'] = 'User removed from a cohort';
+$string['event_cohort_updated'] = 'Cohort updated';
$string['external'] = 'External cohort';
$string['idnumber'] = 'Cohort ID';
$string['memberscount'] = 'Cohort size';
diff --git a/lang/en/completion.php b/lang/en/completion.php
index 5fdeed1b3b0..56cae7725ff 100644
--- a/lang/en/completion.php
+++ b/lang/en/completion.php
@@ -124,6 +124,7 @@ $string['err_nousers'] = 'There are no students on this course or group for whom
$string['err_settingslocked'] = 'One or more students have already completed a criteria so the settings have been locked. Unlocking the completion criteria settings will delete any existing user data and may cause confusion.';
$string['err_system'] = 'An internal error occurred in the completion system. (System administrators can enable debugging information to see more detail.)';
$string['eventcoursecompleted'] = 'Course completed';
+$string['eventcoursecompletionupdated'] = 'Course completion updated';
$string['eventcoursemodulecompletionupdated'] = 'Course module completion updated';
$string['excelcsvdownload'] = 'Download in Excel-compatible format (.csv)';
$string['fraction'] = 'Fraction';
diff --git a/lang/en/moodle.php b/lang/en/moodle.php
index fdbabdbea90..2eaabc8dcd7 100644
--- a/lang/en/moodle.php
+++ b/lang/en/moodle.php
@@ -645,6 +645,7 @@ $string['emailpasswordsent'] = 'Thank you for confirming the change of password.
An email containing your new password has been sent to your address at {$a->email}.
The new password was automatically generated - you might like to
change your password to something easier to remember.';
+$string['emptydragdropregion'] = 'empty region';
$string['enable'] = 'Enable';
$string['encryptedcode'] = 'Encrypted code';
$string['english'] = 'English';
@@ -659,6 +660,13 @@ $string['errorcreatingactivity'] = 'Unable to create an instance of activity \'{
$string['errorfiletoobig'] = 'The file was bigger than the limit of {$a} bytes';
$string['errornouploadrepo'] = 'There is no upload repository enabled for this site';
$string['errorwhenconfirming'] = 'You are not confirmed yet because an error occurred. If you clicked on a link in an email to get here, make sure that the line in your email wasn\'t broken or wrapped. You may have to use cut and paste to reconstruct the link properly.';
+$string['eventcoursecategorydeleted'] = 'Category deleted';
+$string['eventcoursecontentdeleted'] = 'Course content deleted';
+$string['eventcoursecreated'] = 'Course created';
+$string['eventcoursedeleted'] = 'Course deleted';
+$string['eventcourserestored'] = 'Course restored';
+$string['eventcourseupdated'] = 'Course updated';
+$string['eventcoursesectionupdated'] = ' Course section updated';
$string['everybody'] = 'Everybody';
$string['executeat'] = 'Execute at';
$string['existing'] = 'Existing';
@@ -1080,6 +1088,7 @@ $string['moreinformation'] = 'More information about this error';
$string['moreprofileinfoneeded'] = 'Please tell us more about yourself';
$string['mostrecently'] = 'most recently';
$string['move'] = 'Move';
+$string['movecontent'] = 'Move {$a}';
$string['movecategorycontentto'] = 'Move into';
$string['movecategoryto'] = 'Move category to:';
$string['movecontentstoanothercategory'] = 'Move contents to another category';
@@ -1673,6 +1682,7 @@ $string['time'] = 'Time';
$string['timezone'] = 'Timezone';
$string['to'] = 'To';
$string['tocreatenewaccount'] = 'Skip to create new account';
+$string['tocontent'] = 'To item "{$a}"';
$string['today'] = 'Today';
$string['todaylogs'] = 'Today\'s logs';
$string['toeveryone'] = 'to everyone';
diff --git a/lib/accesslib.php b/lib/accesslib.php
index 29276381d3b..386588a29a9 100644
--- a/lib/accesslib.php
+++ b/lib/accesslib.php
@@ -3807,7 +3807,7 @@ function get_users_by_capability(context $context, $capability, $fields = '', $s
$fields = 'u.*';
}
} else {
- if (debugging('', DEBUG_DEVELOPER) && strpos($fields, 'u.*') === false && strpos($fields, 'u.id') === false) {
+ if ($CFG->debugdeveloper && strpos($fields, 'u.*') === false && strpos($fields, 'u.id') === false) {
debugging('u.id must be included in the list of fields passed to get_users_by_capability().', DEBUG_DEVELOPER);
}
}
diff --git a/lib/adminlib.php b/lib/adminlib.php
index 25ae3d0cae1..5d209bda3a2 100644
--- a/lib/adminlib.php
+++ b/lib/adminlib.php
@@ -968,6 +968,8 @@ class admin_category implements parentable_part_of_admin_tree {
* @return bool True if successfully added, false if $something can not be added.
*/
public function add($parentname, $something, $beforesibling = null) {
+ global $CFG;
+
$parent = $this->locate($parentname);
if (is_null($parent)) {
debugging('parent does not exist!');
@@ -979,7 +981,7 @@ class admin_category implements parentable_part_of_admin_tree {
debugging('error - parts of tree can be inserted only into parentable parts');
return false;
}
- if (debugging('', DEBUG_DEVELOPER) && !is_null($this->locate($something->name))) {
+ if ($CFG->debugdeveloper && !is_null($this->locate($something->name))) {
// The name of the node is already used, simply warn the developer that this should not happen.
// It is intentional to check for the debug level before performing the check.
debugging('Duplicate admin page name: ' . $something->name, DEBUG_DEVELOPER);
diff --git a/lib/badgeslib.php b/lib/badgeslib.php
index c8e18af2700..49014568113 100644
--- a/lib/badgeslib.php
+++ b/lib/badgeslib.php
@@ -1025,7 +1025,8 @@ function print_badge_image(badge $badge, stdClass $context, $size = 'small') {
$imageurl = moodle_url::make_pluginfile_url($context->id, 'badges', 'badgeimage', $badge->id, '/', $fsize, false);
// Appending a random parameter to image link to forse browser reload the image.
- $attributes = array('src' => $imageurl . '?' . rand(1, 10000), 'alt' => s($badge->name), 'class' => 'activatebadge');
+ $imageurl->param('refresh', rand(1, 10000));
+ $attributes = array('src' => $imageurl, 'alt' => s($badge->name), 'class' => 'activatebadge');
return html_writer::empty_tag('img', $attributes);
}
diff --git a/lib/classes/component.php b/lib/classes/component.php
index 3f682218022..0fa7c30d5fe 100644
--- a/lib/classes/component.php
+++ b/lib/classes/component.php
@@ -195,12 +195,9 @@ class core_component {
protected static function is_developer() {
global $CFG;
+ // Note we can not rely on $CFG->debug here because DB is not initialised yet.
if (isset($CFG->config_php_settings['debug'])) {
- // Standard moodle script.
$debug = (int)$CFG->config_php_settings['debug'];
- } else if (isset($CFG->debug)) {
- // Usually script with ABORT_AFTER_CONFIG.
- $debug = (int)$CFG->debug;
} else {
return false;
}
diff --git a/lib/classes/event/assessable_submitted.php b/lib/classes/event/assessable_submitted.php
index e206117067f..a889e3e2d70 100644
--- a/lib/classes/event/assessable_submitted.php
+++ b/lib/classes/event/assessable_submitted.php
@@ -49,7 +49,7 @@ abstract class assessable_submitted extends \core\event\base {
*/
protected function init() {
$this->data['crud'] = 'u';
- $this->data['level'] = 50; // TODO MDL-37658.
+ $this->data['level'] = self::LEVEL_PARTICIPATING;
}
/**
diff --git a/lib/classes/event/assessable_uploaded.php b/lib/classes/event/assessable_uploaded.php
index bfd6a5ed054..4e4ed8d346e 100644
--- a/lib/classes/event/assessable_uploaded.php
+++ b/lib/classes/event/assessable_uploaded.php
@@ -49,7 +49,7 @@ abstract class assessable_uploaded extends \core\event\base {
*/
protected function init() {
$this->data['crud'] = 'c';
- $this->data['level'] = 50; // TODO MDL-37658.
+ $this->data['level'] = self::LEVEL_PARTICIPATING;
}
/**
diff --git a/lib/classes/event/base.php b/lib/classes/event/base.php
index 5fcd4047def..0660cf2dc89 100644
--- a/lib/classes/event/base.php
+++ b/lib/classes/event/base.php
@@ -50,6 +50,27 @@ namespace core\event;
* @property-read int $timecreated
*/
abstract class base implements \IteratorAggregate {
+
+ /**
+ * Other level.
+ */
+ const LEVEL_OTHER = 0;
+
+ /**
+ * Teaching level.
+ *
+ * Any event that is performed by someone (typically a teacher) and has a teaching value,
+ * anything that is affecting the learning experience/environment of the students.
+ */
+ const LEVEL_TEACHING = 1;
+
+ /**
+ * Participating level.
+ *
+ * Any event that is performed by a user, and is related (or could be related) to his learning experience.
+ */
+ const LEVEL_PARTICIPATING = 2;
+
/** @var array event data */
protected $data;
@@ -108,7 +129,7 @@ abstract class base implements \IteratorAggregate {
* @throws \coding_exception
*/
public static final function create(array $data = null) {
- global $PAGE, $USER;
+ global $PAGE, $USER, $CFG;
$data = (array)$data;
@@ -178,7 +199,7 @@ abstract class base implements \IteratorAggregate {
}
// Warn developers if they do something wrong.
- if (debugging('', DEBUG_DEVELOPER)) { // This should be replaced by new $CFG->slowdebug flag if introduced.
+ if ($CFG->debugdeveloper) {
static $automatickeys = array('eventname', 'component', 'action', 'target', 'contextlevel', 'contextinstanceid', 'timecreated');
static $initkeys = array('crud', 'level', 'objecttable');
@@ -187,10 +208,10 @@ abstract class base implements \IteratorAggregate {
continue;
} else if (in_array($key, $automatickeys)) {
- debugging("Data key '$key' is not allowed in \\core\\event\\base::create() method, it is set automatically");
+ debugging("Data key '$key' is not allowed in \\core\\event\\base::create() method, it is set automatically", DEBUG_DEVELOPER);
} else if (in_array($key, $initkeys)) {
- debugging("Data key '$key' is not allowed in \\core\\event\\base::create() method, you need to set it in init() method");
+ debugging("Data key '$key' is not allowed in \\core\\event\\base::create() method, you need to set it in init() method", DEBUG_DEVELOPER);
} else if (!in_array($key, self::$fields)) {
debugging("Data key '$key' does not exist in \\core\\event\\base");
@@ -208,8 +229,8 @@ abstract class base implements \IteratorAggregate {
* Override in subclass.
*
* Set all required data properties:
- * 1/ crud - letter [crud] TODO: MDL-37658
- * 2/ level - number 1...100 TODO: MDL-37658
+ * 1/ crud - letter [crud]
+ * 2/ level - using a constant self::LEVEL_*.
* 3/ objecttable - name of database table if objectid specified
*
* Optionally it can set:
@@ -346,7 +367,7 @@ abstract class base implements \IteratorAggregate {
/**
* Return auxiliary data that was stored in logs.
*
- * TODO: MDL-37658
+ * TODO MDL-41331: Properly define this method once logging is finalised.
*
* @return array the format is standardised by logging API
*/
@@ -395,50 +416,51 @@ abstract class base implements \IteratorAggregate {
* @throws \coding_exception
*/
protected final function validate_before_trigger() {
- global $DB;
+ global $DB, $CFG;
if (empty($this->data['crud'])) {
throw new \coding_exception('crud must be specified in init() method of each method');
}
- if (empty($this->data['level'])) {
+ if (!isset($this->data['level'])) {
throw new \coding_exception('level must be specified in init() method of each method');
}
if (!empty($this->data['objectid']) and empty($this->data['objecttable'])) {
throw new \coding_exception('objecttable must be specified in init() method if objectid present');
}
- if (debugging('', DEBUG_DEVELOPER)) { // This should be replaced by new $CFG->slowdebug flag if introduced.
+ if ($CFG->debugdeveloper) {
// Ideally these should be coding exceptions, but we need to skip these for performance reasons
// on production servers.
if (!in_array($this->data['crud'], array('c', 'r', 'u', 'd'), true)) {
- debugging("Invalid event crud value specified.");
+ debugging("Invalid event crud value specified.", DEBUG_DEVELOPER);
}
- if (!is_number($this->data['level'])) {
- debugging('Event property level must be a number');
+ if (!in_array($this->data['level'], array(self::LEVEL_OTHER, self::LEVEL_TEACHING, self::LEVEL_PARTICIPATING))) {
+ // Bitwise combination of levels is not allowed at this stage.
+ debugging('Event property level must a constant value, see event_base::LEVEL_*', DEBUG_DEVELOPER);
}
if (self::$fields !== array_keys($this->data)) {
- debugging('Number of event data fields must not be changed in event classes');
+ debugging('Number of event data fields must not be changed in event classes', DEBUG_DEVELOPER);
}
$encoded = json_encode($this->data['other']);
if ($encoded === false or $this->data['other'] !== json_decode($encoded, true)) {
- debugging('other event data must be compatible with json encoding');
+ debugging('other event data must be compatible with json encoding', DEBUG_DEVELOPER);
}
if ($this->data['userid'] and !is_number($this->data['userid'])) {
- debugging('Event property userid must be a number');
+ debugging('Event property userid must be a number', DEBUG_DEVELOPER);
}
if ($this->data['courseid'] and !is_number($this->data['courseid'])) {
- debugging('Event property courseid must be a number');
+ debugging('Event property courseid must be a number', DEBUG_DEVELOPER);
}
if ($this->data['objectid'] and !is_number($this->data['objectid'])) {
- debugging('Event property objectid must be a number');
+ debugging('Event property objectid must be a number', DEBUG_DEVELOPER);
}
if ($this->data['relateduserid'] and !is_number($this->data['relateduserid'])) {
- debugging('Event property relateduserid must be a number');
+ debugging('Event property relateduserid must be a number', DEBUG_DEVELOPER);
}
if ($this->data['objecttable']) {
if (!$DB->get_manager()->table_exists($this->data['objecttable'])) {
- debugging('Unknown table specified in objecttable field');
+ debugging('Unknown table specified in objecttable field', DEBUG_DEVELOPER);
}
}
}
@@ -521,7 +543,7 @@ abstract class base implements \IteratorAggregate {
* @throws \coding_exception if used after ::trigger()
*/
public final function add_record_snapshot($tablename, $record) {
- global $DB;
+ global $DB, $CFG;
if ($this->triggered) {
throw new \coding_exception('It is not possible to add snapshots after triggering of events');
@@ -529,9 +551,9 @@ abstract class base implements \IteratorAggregate {
// NOTE: this might use some kind of MUC cache,
// hopefully we will not run out of memory here...
- if (debugging('', DEBUG_DEVELOPER)) { // This should be replaced by new $CFG->slowdebug flag if introduced.
+ if ($CFG->debugdeveloper) {
if (!$DB->get_manager()->table_exists($tablename)) {
- debugging("Invalid table name '$tablename' specified, database table does not exist.");
+ debugging("Invalid table name '$tablename' specified, database table does not exist.", DEBUG_DEVELOPER);
}
}
$this->recordsnapshots[$tablename][$record->id] = $record;
diff --git a/lib/classes/event/blog_entry_created.php b/lib/classes/event/blog_entry_created.php
index 9c2718b3334..8f686472aa2 100644
--- a/lib/classes/event/blog_entry_created.php
+++ b/lib/classes/event/blog_entry_created.php
@@ -45,8 +45,7 @@ class blog_entry_created extends \core\event\base {
$this->context = \context_system::instance();
$this->data['objecttable'] = 'post';
$this->data['crud'] = 'c';
- // TODO: MDL-37658 set level.
- $this->data['level'] = 50;
+ $this->data['level'] = self::LEVEL_PARTICIPATING;
}
/**
diff --git a/lib/classes/event/blog_entry_deleted.php b/lib/classes/event/blog_entry_deleted.php
index 9de612d024d..9d02aab3976 100644
--- a/lib/classes/event/blog_entry_deleted.php
+++ b/lib/classes/event/blog_entry_deleted.php
@@ -44,8 +44,7 @@ class blog_entry_deleted extends \core\event\base {
$this->context = \context_system::instance();
$this->data['objecttable'] = 'post';
$this->data['crud'] = 'd';
- // TODO: MDL-37658 set level.
- $this->data['level'] = 50;
+ $this->data['level'] = self::LEVEL_PARTICIPATING;
}
/**
diff --git a/lib/classes/event/cohort_created.php b/lib/classes/event/cohort_created.php
new file mode 100644
index 00000000000..0db4d7b6839
--- /dev/null
+++ b/lib/classes/event/cohort_created.php
@@ -0,0 +1,92 @@
+.
+
+/**
+ * Cohort updated event.
+ *
+ * @package core
+ * @copyright 2013 Dan Poltawski
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core\event;
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Cohort created event class.
+ *
+ * @package core
+ * @copyright 2013 Dan Poltawski
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class cohort_created extends base {
+
+ /**
+ * Init method.
+ *
+ * @return void
+ */
+ protected function init() {
+ $this->data['crud'] = 'c';
+ $this->data['level'] = self::LEVEL_OTHER;
+ $this->data['objecttable'] = 'cohort';
+ }
+
+ /**
+ * Returns localised general event name.
+ *
+ * @return string
+ */
+ public static function get_name() {
+ return get_string('event_cohort_created', 'core_cohort');
+ }
+
+ /**
+ * Returns description of what happened.
+ *
+ * @return string
+ */
+ public function get_description() {
+ return 'Cohort '.$this->objectid.' was created by '.$this->userid.' at context '.$this->contextid;
+ }
+
+ /**
+ * Returns relevant URL.
+ *
+ * @return \moodle_url
+ */
+ public function get_url() {
+ return new \moodle_url('/cohort/index.php', array('contextid' => $this->contextid));
+ }
+
+ /**
+ * Return legacy event name.
+ *
+ * @return string legacy event name
+ */
+ public static function get_legacy_eventname() {
+ return 'cohort_added';
+ }
+
+ /**
+ * Return legacy event data.
+ *
+ * @return stdClass
+ */
+ protected function get_legacy_eventdata() {
+ return $this->get_record_snapshot('cohort', $this->objectid);
+ }
+}
diff --git a/lib/classes/event/cohort_deleted.php b/lib/classes/event/cohort_deleted.php
new file mode 100644
index 00000000000..b43b110032f
--- /dev/null
+++ b/lib/classes/event/cohort_deleted.php
@@ -0,0 +1,92 @@
+.
+
+/**
+ * Cohort deleted event.
+ *
+ * @package core
+ * @copyright 2013 Dan Poltawski
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core\event;
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Cohort deleted event class.
+ *
+ * @package core
+ * @copyright 2013 Dan Poltawski
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class cohort_deleted extends base {
+
+ /**
+ * Init method.
+ *
+ * @return void
+ */
+ protected function init() {
+ $this->data['crud'] = 'd';
+ $this->data['level'] = self::LEVEL_OTHER;
+ $this->data['objecttable'] = 'cohort';
+ }
+
+ /**
+ * Returns localised general event name.
+ *
+ * @return string
+ */
+ public static function get_name() {
+ return get_string('event_core_deleted', 'core_cohort');
+ }
+
+ /**
+ * Returns description of what happened.
+ *
+ * @return string
+ */
+ public function get_description() {
+ return 'Cohort '.$this->objectid.' was deleted by '.$this->userid.' from context '.$this->contextid;
+ }
+
+ /**
+ * Returns relevant URL.
+ *
+ * @return \moodle_url
+ */
+ public function get_url() {
+ return new \moodle_url('/cohort/index.php', array('contextid' => $this->contextid));
+ }
+
+ /**
+ * Return legacy event name.
+ *
+ * @return null|string legacy event name
+ */
+ public static function get_legacy_eventname() {
+ return 'cohort_deleted';
+ }
+
+ /**
+ * Return legacy event data.
+ *
+ * @return stdClass
+ */
+ protected function get_legacy_eventdata() {
+ return $this->get_record_snapshot('cohort', $this->objectid);
+ }
+}
diff --git a/lib/classes/event/cohort_member_added.php b/lib/classes/event/cohort_member_added.php
new file mode 100644
index 00000000000..a7e52eb0c4d
--- /dev/null
+++ b/lib/classes/event/cohort_member_added.php
@@ -0,0 +1,95 @@
+.
+
+/**
+ * User added to a cohort event.
+ *
+ * @package core
+ * @copyright 2013 Dan Poltawski
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core\event;
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * User added to a cohort event class.
+ *
+ * @package core
+ * @copyright 2013 Dan Poltawski
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class cohort_member_added extends base {
+
+ /**
+ * Init method.
+ *
+ * @return void
+ */
+ protected function init() {
+ $this->data['crud'] = 'c';
+ $this->data['level'] = self::LEVEL_OTHER;
+ $this->data['objecttable'] = 'cohort';
+ }
+
+ /**
+ * Returns localised general event name.
+ *
+ * @return string
+ */
+ public static function get_name() {
+ return get_string('event_cohort_member_added', 'core_cohort');
+ }
+
+ /**
+ * Returns description of what happened.
+ *
+ * @return string
+ */
+ public function get_description() {
+ return 'User '.$this->relateduserid.' was added to cohort '.$this->objectid.' by user '.$this->userid;
+ }
+
+ /**
+ * Returns relevant URL.
+ *
+ * @return \moodle_url
+ */
+ public function get_url() {
+ return new \moodle_url('/cohort/assign.php', array('id' => $this->objectid));
+ }
+
+ /**
+ * Return legacy event name.
+ *
+ * @return string legacy event name.
+ */
+ public static function get_legacy_eventname() {
+ return 'cohort_member_added';
+ }
+
+ /**
+ * Return legacy event data.
+ *
+ * @return stdClass
+ */
+ protected function get_legacy_eventdata() {
+ $data = new \stdClass();
+ $data->cohortid = $this->objectid;
+ $data->userid = $this->relateduserid;
+ return $data;
+ }
+}
diff --git a/lib/classes/event/cohort_member_removed.php b/lib/classes/event/cohort_member_removed.php
new file mode 100644
index 00000000000..b3f47bd7d50
--- /dev/null
+++ b/lib/classes/event/cohort_member_removed.php
@@ -0,0 +1,96 @@
+.
+
+/**
+ * User removed from a cohort event.
+ *
+ * @package core
+ * @copyright 2013 Dan Poltawski
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core\event;
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * User removed from a cohort event class.
+ *
+ * @package core
+ * @copyright 2013 Dan Poltawski
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+class cohort_member_removed extends base {
+
+ /**
+ * Init method.
+ *
+ * @return void
+ */
+ protected function init() {
+ $this->data['crud'] = 'd';
+ $this->data['level'] = self::LEVEL_OTHER;
+ $this->data['objecttable'] = 'cohort';
+ }
+
+ /**
+ * Returns localised general event name.
+ *
+ * @return string
+ */
+ public static function get_name() {
+ return get_string('event_cohort_member_removed', 'core_cohort');
+ }
+
+ /**
+ * Returns description of what happened.
+ *
+ * @return string
+ */
+ public function get_description() {
+ return 'User '.$this->relateduserid.' was removed from cohort '.$this->objectid.' by user '.$this->userid;
+ }
+
+ /**
+ * Returns relevant URL.
+ *
+ * @return \moodle_url
+ */
+ public function get_url() {
+ return new \moodle_url('/cohort/assign.php', array('id' => $this->objectid));
+ }
+
+ /**
+ * Return legacy event name.
+ *
+ * @return string legacy event name.
+ */
+ public static function get_legacy_eventname() {
+ return 'cohort_member_removed';
+ }
+
+ /**
+ * Return legacy event data.
+ *
+ * @return stdClass
+ */
+ protected function get_legacy_eventdata() {
+ $data = new \stdClass();
+ $data->cohortid = $this->objectid;
+ $data->userid = $this->relateduserid;
+ return $data;
+ }
+}
diff --git a/lib/classes/event/cohort_updated.php b/lib/classes/event/cohort_updated.php
new file mode 100644
index 00000000000..f9b6a0756d8
--- /dev/null
+++ b/lib/classes/event/cohort_updated.php
@@ -0,0 +1,92 @@
+.
+
+/**
+ * Cohort updated event.
+ *
+ * @package core
+ * @copyright 2013 Dan Poltawski
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core\event;
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Cohort updated event class.
+ *
+ * @package core
+ * @copyright 2013 Dan Poltawski
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class cohort_updated extends base {
+
+ /**
+ * Init method.
+ *
+ * @return void
+ */
+ protected function init() {
+ $this->data['crud'] = 'u';
+ $this->data['level'] = self::LEVEL_OTHER;
+ $this->data['objecttable'] = 'cohort';
+ }
+
+ /**
+ * Returns localised general event name.
+ *
+ * @return string
+ */
+ public static function get_name() {
+ return get_string('event_cohort_updated', 'core_cohort');
+ }
+
+ /**
+ * Returns description of what happened.
+ *
+ * @return string
+ */
+ public function get_description() {
+ return 'Cohort '.$this->objectid.' was updated by '.$this->userid.' at context '.$this->contextid;
+ }
+
+ /**
+ * Returns relevant URL.
+ *
+ * @return \moodle_url
+ */
+ public function get_url() {
+ return new \moodle_url('/cohort/edit.php', array('id' => $this->objectid));
+ }
+
+ /**
+ * Return legacy event name.
+ *
+ * @return string legacy event name.
+ */
+ public static function get_legacy_eventname() {
+ return 'cohort_updated';
+ }
+
+ /**
+ * Return legacy event data.
+ *
+ * @return stdClass
+ */
+ protected function get_legacy_eventdata() {
+ return $this->get_record_snapshot('cohort', $this->objectid);
+ }
+}
diff --git a/lib/classes/event/course_category_deleted.php b/lib/classes/event/course_category_deleted.php
new file mode 100644
index 00000000000..8f0b4d32c3b
--- /dev/null
+++ b/lib/classes/event/course_category_deleted.php
@@ -0,0 +1,95 @@
+.
+
+namespace core\event;
+
+/**
+ * category deleted event.
+ *
+ * @package core
+ * @copyright 2013 Mark Nelson
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class course_category_deleted extends base {
+
+ /**
+ * The course category class used for legacy reasons.
+ */
+ private $coursecat;
+
+ /**
+ * Initialise the event data.
+ */
+ protected function init() {
+ $this->data['objecttable'] = 'course_categories';
+ $this->data['crud'] = 'd';
+ $this->data['level'] = self::LEVEL_OTHER;
+ }
+
+ /**
+ * Returns localised general event name.
+ *
+ * @return string
+ */
+ public static function get_name() {
+ return get_string('eventcoursecategorydeleted');
+ }
+
+ /**
+ * Returns non-localised description of what happened.
+ *
+ * @return string
+ */
+ public function get_description() {
+ return "Category {$this->objectid} was deleted by user {$this->userid}";
+ }
+
+ /**
+ * Returns the name of the legacy event.
+ *
+ * @return string legacy event name
+ */
+ public static function get_legacy_eventname() {
+ return 'course_category_deleted';
+ }
+
+ /**
+ * Returns the legacy event data.
+ *
+ * @return coursecat the category that was deleted
+ */
+ protected function get_legacy_eventdata() {
+ return $this->coursecat;
+ }
+
+ /**
+ * Set the legacy event data.
+ *
+ * @param coursecat $class instance of the coursecat class
+ */
+ public function set_legacy_eventdata($class) {
+ $this->coursecat = $class;
+ }
+
+ /**
+ * Return legacy data for add_to_log().
+ *
+ * @return array
+ */
+ protected function get_legacy_logdata() {
+ return array(SITEID, 'category', 'delete', 'index.php', $this->other['name'] . '(ID ' . $this->objectid . ')');
+ }
+}
diff --git a/lib/classes/event/course_completed.php b/lib/classes/event/course_completed.php
index e57ddae9245..3c7389a788a 100644
--- a/lib/classes/event/course_completed.php
+++ b/lib/classes/event/course_completed.php
@@ -31,8 +31,7 @@ class course_completed extends base {
protected function init() {
$this->data['objecttable'] = 'course_completions';
$this->data['crud'] = 'u';
- // TODO: MDL-37658 set level.
- $this->data['level'] = 50;
+ $this->data['level'] = self::LEVEL_PARTICIPATING;
}
/**
diff --git a/lib/classes/event/course_completion_updated.php b/lib/classes/event/course_completion_updated.php
new file mode 100644
index 00000000000..bd1dcbd5be1
--- /dev/null
+++ b/lib/classes/event/course_completion_updated.php
@@ -0,0 +1,81 @@
+.
+
+/**
+ * Event when course module completion is updated.
+ *
+ * @package core
+ * @copyright 2013 Rajesh Taneja
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Event when course module completion is updated.
+ *
+ * @package core
+ * @copyright 2013 Rajesh Taneja
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class course_completion_updated extends base {
+
+ /**
+ * Initialise required event data properties.
+ */
+ protected function init() {
+ $this->data['crud'] = 'u';
+ $this->data['level'] = self::LEVEL_PARTICIPATING;
+ }
+
+ /**
+ * Returns localised event name.
+ *
+ * @return string
+ */
+ public static function get_name() {
+ return new get_string('eventcoursecompletionupdated', 'core_completion');
+ }
+
+ /**
+ * Returns non-localised event description with id's for admin use only.
+ *
+ * @return string
+ */
+ public function get_description() {
+ return 'Course completion for course' . $this->courseid . ' is updated by user ' . $this->userid;
+ }
+
+ /**
+ * Returns relevant URL.
+ *
+ * @return \moodle_url
+ */
+ public function get_url() {
+ return new moodle_url('/course/completion.php', array('id' => $this->courseid));
+ }
+
+ /**
+ * Return legacy add_to_log() data.
+ *
+ * @return array of parameters to be passed to legacy add_to_log() function.
+ */
+ protected function get_legacy_logdata() {
+ return array($this->courseid, 'course', 'completion updated', 'completion.php?id=' . $this->courseid);
+ }
+}
diff --git a/lib/classes/event/course_content_deleted.php b/lib/classes/event/course_content_deleted.php
new file mode 100644
index 00000000000..9cb33098224
--- /dev/null
+++ b/lib/classes/event/course_content_deleted.php
@@ -0,0 +1,76 @@
+.
+
+namespace core\event;
+
+/**
+ * Course content_deleted event.
+ *
+ * @package core
+ * @copyright 2013 Mark Nelson
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class course_content_deleted extends base {
+
+ /**
+ * Initialise the event data.
+ */
+ protected function init() {
+ $this->data['objecttable'] = 'course';
+ $this->data['crud'] = 'd';
+ $this->data['level'] = self::LEVEL_TEACHING;
+ }
+
+ /**
+ * Returns localised general event name.
+ *
+ * @return string
+ */
+ public static function get_name() {
+ return get_string('eventcoursecontentdeleted');
+ }
+
+ /**
+ * Returns non-localised description of what happened.
+ *
+ * @return string
+ */
+ public function get_description() {
+ return "Course content was deleted by user {$this->userid}";
+ }
+
+ /**
+ * Returns the name of the legacy event.
+ *
+ * @return string legacy event name
+ */
+ public static function get_legacy_eventname() {
+ return 'course_content_removed';
+ }
+
+ /**
+ * Returns the legacy event data.
+ *
+ * @return \stdClass the course the content was deleted from
+ */
+ protected function get_legacy_eventdata() {
+ $course = $this->get_record_snapshot('course', $this->objectid);
+ $course->context = $this->context;
+ $course->options = $this->other['options'];
+
+ return $course;
+ }
+}
diff --git a/lib/classes/event/course_created.php b/lib/classes/event/course_created.php
new file mode 100644
index 00000000000..040f830f6c1
--- /dev/null
+++ b/lib/classes/event/course_created.php
@@ -0,0 +1,90 @@
+.
+
+namespace core\event;
+
+/**
+ * Course created event.
+ *
+ * @package core
+ * @copyright 2013 Mark Nelson
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class course_created extends base {
+
+ /**
+ * Initialise the event data.
+ */
+ protected function init() {
+ $this->data['objecttable'] = 'course';
+ $this->data['crud'] = 'c';
+ $this->data['level'] = self::LEVEL_TEACHING;
+ }
+
+ /**
+ * Returns localised general event name.
+ *
+ * @return string
+ */
+ public static function get_name() {
+ return get_string('eventcoursecreated');
+ }
+
+ /**
+ * Returns non-localised description of what happened.
+ *
+ * @return string
+ */
+ public function get_description() {
+ return "Course {$this->objectid} was created by user {$this->userid}";
+ }
+
+ /**
+ * Returns relevant URL.
+ *
+ * @return \moodle_url
+ */
+ public function get_url() {
+ return new \moodle_url('/course/view.php', array('id' => $this->objectid));
+ }
+
+ /**
+ * Returns the name of the legacy event.
+ *
+ * @return string legacy event name
+ */
+ public static function get_legacy_eventname() {
+ return 'course_created';
+ }
+
+ /**
+ * Returns the legacy event data.
+ *
+ * @return \stdClass the course that was created
+ */
+ protected function get_legacy_eventdata() {
+ return $this->get_record_snapshot('course', $this->objectid);
+ }
+
+ /**
+ * Return legacy data for add_to_log().
+ *
+ * @return array
+ */
+ protected function get_legacy_logdata() {
+ return array(SITEID, 'course', 'new', 'view.php?id=' . $this->objectid, $this->other['fullname'] . ' (ID ' . $this->objectid . ')');
+ }
+}
diff --git a/lib/classes/event/course_deleted.php b/lib/classes/event/course_deleted.php
new file mode 100644
index 00000000000..2cf551f98f8
--- /dev/null
+++ b/lib/classes/event/course_deleted.php
@@ -0,0 +1,84 @@
+.
+
+namespace core\event;
+
+/**
+ * Course deleted event.
+ *
+ * @package core
+ * @copyright 2013 Mark Nelson
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class course_deleted extends base {
+
+ /**
+ * Initialise the event data.
+ */
+ protected function init() {
+ $this->data['objecttable'] = 'course';
+ $this->data['crud'] = 'd';
+ $this->data['level'] = self::LEVEL_TEACHING;
+ }
+
+ /**
+ * Returns localised general event name.
+ *
+ * @return string
+ */
+ public static function get_name() {
+ return get_string('eventcoursedeleted');
+ }
+
+ /**
+ * Returns non-localised description of what happened.
+ *
+ * @return string
+ */
+ public function get_description() {
+ return "Course {$this->courseid} was deleted by user {$this->userid}";
+ }
+
+ /**
+ * Returns the name of the legacy event.
+ *
+ * @return string legacy event name
+ */
+ public static function get_legacy_eventname() {
+ return 'course_deleted';
+ }
+
+ /**
+ * Returns the legacy event data.
+ *
+ * @return \stdClass the course that was deleted
+ */
+ protected function get_legacy_eventdata() {
+ $course = $this->get_record_snapshot('course', $this->objectid);
+ $course->context = $this->context;
+
+ return $course;
+ }
+
+ /**
+ * Return legacy data for add_to_log().
+ *
+ * @return array
+ */
+ protected function get_legacy_logdata() {
+ return array(SITEID, 'course', 'delete', 'view.php?id=' . $this->objectid, $this->other['fullname'] . '(ID ' . $this->objectid . ')');
+ }
+}
diff --git a/lib/classes/event/course_module_completion_updated.php b/lib/classes/event/course_module_completion_updated.php
index c57f0c73d4a..fbc8c7bf251 100644
--- a/lib/classes/event/course_module_completion_updated.php
+++ b/lib/classes/event/course_module_completion_updated.php
@@ -31,8 +31,7 @@ class course_module_completion_updated extends base {
protected function init() {
$this->data['objecttable'] = 'course_modules_completion';
$this->data['crud'] = 'u';
- // TODO: MDL-37658 set level.
- $this->data['level'] = 50;
+ $this->data['level'] = self::LEVEL_PARTICIPATING;
}
/**
diff --git a/lib/classes/event/course_restored.php b/lib/classes/event/course_restored.php
new file mode 100644
index 00000000000..f684463cfb7
--- /dev/null
+++ b/lib/classes/event/course_restored.php
@@ -0,0 +1,89 @@
+.
+
+namespace core\event;
+
+/**
+ * Course restored event.
+ *
+ * @package core
+ * @copyright 2013 Mark Nelson
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class course_restored extends base {
+
+ /**
+ * Initialise the event data.
+ */
+ protected function init() {
+ $this->data['objecttable'] = 'course';
+ $this->data['crud'] = 'c';
+ $this->data['level'] = self::LEVEL_TEACHING;
+ }
+
+ /**
+ * Returns localised general event name.
+ *
+ * @return string
+ */
+ public static function get_name() {
+ return get_string('eventcourserestored');
+ }
+
+ /**
+ * Returns non-localised description of what happened.
+ *
+ * @return string
+ */
+ public function get_description() {
+ return "Course {$this->objectid} was restored by user {$this->userid}";
+ }
+
+ /**
+ * Returns relevant URL.
+ *
+ * @return \moodle_url
+ */
+ public function get_url() {
+ return new \moodle_url('/course/view.php', array('id' => $this->objectid));
+ }
+
+ /**
+ * Returns the name of the legacy event.
+ *
+ * @return string legacy event name
+ */
+ public static function get_legacy_eventname() {
+ return 'course_restored';
+ }
+
+ /**
+ * Returns the legacy event data.
+ *
+ * @return \stdClass the legacy event data
+ */
+ protected function get_legacy_eventdata() {
+ return (object) array(
+ 'courseid' => $this->objectid,
+ 'userid' => $this->userid,
+ 'type' => $this->other['type'],
+ 'target' => $this->other['target'],
+ 'mode' => $this->other['mode'],
+ 'operation' => $this->other['operation'],
+ 'samesite' => $this->other['samesite'],
+ );
+ }
+}
diff --git a/lib/classes/event/course_section_updated.php b/lib/classes/event/course_section_updated.php
new file mode 100644
index 00000000000..5a7d9e06a65
--- /dev/null
+++ b/lib/classes/event/course_section_updated.php
@@ -0,0 +1,85 @@
+.
+
+/**
+ * Course section updated.
+ *
+ * @package core
+ * @copyright 2013 Rajesh Taneja
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Course section updated.
+ *
+ * @package core
+ * @copyright 2013 Rajesh Taneja
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class course_section_updated extends base {
+
+ /**
+ * Init method.
+ *
+ * @return void
+ */
+ protected function init() {
+ $this->data['objecttable'] = 'course_sections';
+ $this->data['crud'] = 'u';
+ $this->data['level'] = self::LEVEL_TEACHING;
+ }
+
+ /**
+ * Return localised event name.
+ *
+ * @return string
+ */
+ public static function get_name() {
+ return get_string('eventcoursesectionupdated');
+ }
+
+ /**
+ * Returns non-localised event description with id's for admin use only.
+ *
+ * @return string
+ */
+ public function get_description() {
+ return 'Course ' . $this->courseid . ' section ' . $this->other['sectionnum'] . ' updated by user ' . $this->userid;
+ }
+
+ /**
+ * Get URL related to the action.
+ *
+ * @return \moodle_url
+ */
+ public function get_url() {
+ return new \moodle_url('/course/editsection.php', array('id' => $this->objectid));
+ }
+
+ /**
+ * Return legacy data for add_to_log().
+ *
+ * @return array
+ */
+ protected function get_legacy_logdata() {
+ $sectiondata = $this->get_record_snapshot('course_sections', $this->objectid);
+ return array($this->courseid, 'course', 'editsection', 'editsection.php?id=' . $this->objectid, $sectiondata->section);
+ }
+}
diff --git a/lib/classes/event/course_updated.php b/lib/classes/event/course_updated.php
new file mode 100644
index 00000000000..b76dd65b8e8
--- /dev/null
+++ b/lib/classes/event/course_updated.php
@@ -0,0 +1,102 @@
+.
+
+namespace core\event;
+
+/**
+ * Course updated event.
+ *
+ * @package core
+ * @copyright 2013 Mark Nelson
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class course_updated extends base {
+
+ /** @var array The legacy log data. */
+ private $legacylogdata;
+
+ /**
+ * Initialise the event data.
+ */
+ protected function init() {
+ $this->data['objecttable'] = 'course';
+ $this->data['crud'] = 'u';
+ $this->data['level'] = self::LEVEL_TEACHING;
+ }
+
+ /**
+ * Returns localised general event name.
+ *
+ * @return string
+ */
+ public static function get_name() {
+ return get_string('eventcourseupdated');
+ }
+
+ /**
+ * Returns non-localised description of what happened.
+ *
+ * @return string
+ */
+ public function get_description() {
+ return "Course {$this->courseid} was updated by user {$this->userid}";
+ }
+
+ /**
+ * Returns relevant URL.
+ *
+ * @return \moodle_url
+ */
+ public function get_url() {
+ return new \moodle_url('/course/view.php', array('id' => $this->objectid));
+ }
+
+ /**
+ * Returns the name of the legacy event.
+ *
+ * @return string legacy event name
+ */
+ public static function get_legacy_eventname() {
+ return 'course_updated';
+ }
+
+ /**
+ * Returns the legacy event data.
+ *
+ * @return \stdClass the course that was updated
+ */
+ protected function get_legacy_eventdata() {
+ return $this->get_record_snapshot('course', $this->objectid);
+ }
+
+ /**
+ * Set the legacy data used for add_to_log().
+ *
+ * @param array $logdata
+ */
+ public function set_legacy_logdata($logdata) {
+ $this->legacylogdata = $logdata;
+ }
+
+ /**
+ * Return legacy data for add_to_log().
+ *
+ * @return array
+ */
+ protected function get_legacy_logdata() {
+ return $this->legacylogdata;
+ }
+}
diff --git a/lib/classes/event/role_allow_assign_updated.php b/lib/classes/event/role_allow_assign_updated.php
index 538e56c77d5..ad4ac9af5f8 100644
--- a/lib/classes/event/role_allow_assign_updated.php
+++ b/lib/classes/event/role_allow_assign_updated.php
@@ -30,8 +30,7 @@ class role_allow_assign_updated extends base {
*/
protected function init() {
$this->data['crud'] = 'u';
- // TODO: MDL-41040 set level.
- $this->data['level'] = 50;
+ $this->data['level'] = self::LEVEL_OTHER;
}
/**
diff --git a/lib/classes/event/role_allow_override_updated.php b/lib/classes/event/role_allow_override_updated.php
index 2edee4b3769..fc7c5ce687b 100644
--- a/lib/classes/event/role_allow_override_updated.php
+++ b/lib/classes/event/role_allow_override_updated.php
@@ -30,8 +30,7 @@ class role_allow_override_updated extends base {
*/
protected function init() {
$this->data['crud'] = 'u';
- // TODO: MDL-41040 set level.
- $this->data['level'] = 50;
+ $this->data['level'] = self::LEVEL_OTHER;
}
/**
diff --git a/lib/classes/event/role_allow_switch_updated.php b/lib/classes/event/role_allow_switch_updated.php
index 04606e7e51f..20e45eb5ba2 100644
--- a/lib/classes/event/role_allow_switch_updated.php
+++ b/lib/classes/event/role_allow_switch_updated.php
@@ -30,8 +30,7 @@ class role_allow_switch_updated extends base {
*/
protected function init() {
$this->data['crud'] = 'u';
- // TODO: MDL-41040 set level.
- $this->data['level'] = 50;
+ $this->data['level'] = self::LEVEL_OTHER;
}
/**
diff --git a/lib/classes/event/role_assigned.php b/lib/classes/event/role_assigned.php
index 9e748393ed5..1f763abb8e1 100644
--- a/lib/classes/event/role_assigned.php
+++ b/lib/classes/event/role_assigned.php
@@ -28,8 +28,7 @@ class role_assigned extends base {
protected function init() {
$this->data['objecttable'] = 'role';
$this->data['crud'] = 'c';
- // TODO: MDL-37658 set level
- $this->data['level'] = 50;
+ $this->data['level'] = self::LEVEL_OTHER;
}
/**
diff --git a/lib/classes/event/role_capabilities_updated.php b/lib/classes/event/role_capabilities_updated.php
index 6f836dcf80f..5d1f89ca329 100644
--- a/lib/classes/event/role_capabilities_updated.php
+++ b/lib/classes/event/role_capabilities_updated.php
@@ -34,8 +34,7 @@ class role_capabilities_updated extends base {
protected function init() {
$this->data['objecttable'] = 'role';
$this->data['crud'] = 'u';
- // TODO: MDL-41040 set level.
- $this->data['level'] = 50;
+ $this->data['level'] = self::LEVEL_OTHER;
}
/**
diff --git a/lib/classes/event/role_deleted.php b/lib/classes/event/role_deleted.php
index 11e15b9f858..969ebbb5e8a 100644
--- a/lib/classes/event/role_deleted.php
+++ b/lib/classes/event/role_deleted.php
@@ -31,8 +31,7 @@ class role_deleted extends base {
protected function init() {
$this->data['objecttable'] = 'role';
$this->data['crud'] = 'd';
- // TODO: MDL-41040 set level.
- $this->data['level'] = 50;
+ $this->data['level'] = self::LEVEL_OTHER;
}
/**
diff --git a/lib/classes/event/role_unassigned.php b/lib/classes/event/role_unassigned.php
index 3bb4a100e90..4735797b303 100644
--- a/lib/classes/event/role_unassigned.php
+++ b/lib/classes/event/role_unassigned.php
@@ -28,8 +28,7 @@ class role_unassigned extends base {
protected function init() {
$this->data['objecttable'] = 'role';
$this->data['crud'] = 'd';
- // TODO: MDL-37658 set level
- $this->data['level'] = 50;
+ $this->data['level'] = self::LEVEL_OTHER;
}
/**
diff --git a/lib/classes/event/user_loggedin.php b/lib/classes/event/user_loggedin.php
index 7cfc3f847ce..2f842fa1edd 100644
--- a/lib/classes/event/user_loggedin.php
+++ b/lib/classes/event/user_loggedin.php
@@ -89,7 +89,7 @@ class user_loggedin extends \core\event\base {
protected function init() {
$this->context = \context_system::instance();
$this->data['crud'] = 'r';
- $this->data['level'] = 50; // TODO MDL-37658.
+ $this->data['level'] = self::LEVEL_OTHER;
$this->data['objecttable'] = 'user';
}
diff --git a/lib/classes/event/user_loggedinas.php b/lib/classes/event/user_loggedinas.php
new file mode 100644
index 00000000000..50012d8cffb
--- /dev/null
+++ b/lib/classes/event/user_loggedinas.php
@@ -0,0 +1,85 @@
+.
+
+/**
+ * User loggedinas event.
+ *
+ * @package core
+ * @copyright 2013 Rajesh Taneja
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * User loggedinas event class.
+ *
+ * @package core
+ * @copyright 2013 Rajesh Taneja
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class user_loggedinas extends base {
+
+ /**
+ * Init method.
+ *
+ * @return void
+ */
+ protected function init() {
+ $this->data['crud'] = 'r';
+ $this->data['level'] = self::LEVEL_OTHER;
+ $this->data['objecttable'] = 'user';
+ }
+
+ /**
+ * Return localised event name.
+ *
+ * @return string
+ */
+ public static function get_name() {
+ return get_string('eventuserloggedinas', 'auth');
+ }
+
+ /**
+ * Returns non-localised event description with id's for admin use only.
+ *
+ * @return string
+ */
+ public function get_description() {
+ return 'Userid ' . $this->userid . ' has logged in as '. $this->relateduserid;
+ }
+
+ /**
+ * Return legacy data for add_to_log().
+ *
+ * @return array
+ */
+ protected function get_legacy_logdata() {
+ return array($this->courseid, 'course', 'loginas', '../user/view.php?id=' . $this->courseid . '&user=' . $this->userid,
+ $this->other['originalusername'] . ' -> ' . $this->other['loggedinasusername']);
+ }
+
+ /**
+ * Get URL related to the action.
+ *
+ * @return \moodle_url
+ */
+ public function get_url() {
+ return new \moodle_url('/user/view.php', array('id' => $this->objectid));
+ }
+}
diff --git a/lib/coursecatlib.php b/lib/coursecatlib.php
index 816c001d9b2..0a5164939d9 100644
--- a/lib/coursecatlib.php
+++ b/lib/coursecatlib.php
@@ -1366,6 +1366,7 @@ class coursecat implements renderable, cacheable_object, IteratorAggregate {
*/
public function delete_full($showfeedback = true) {
global $CFG, $DB;
+
require_once($CFG->libdir.'/gradelib.php');
require_once($CFG->libdir.'/questionlib.php');
require_once($CFG->dirroot.'/cohort/lib.php');
@@ -1400,12 +1401,20 @@ class coursecat implements renderable, cacheable_object, IteratorAggregate {
// finally delete the category and it's context
$DB->delete_records('course_categories', array('id' => $this->id));
- context_helper::delete_instance(CONTEXT_COURSECAT, $this->id);
- add_to_log(SITEID, "category", "delete", "index.php", "$this->name (ID $this->id)");
+
+ $coursecatcontext = context_coursecat::instance($this->id);
+ $coursecatcontext->delete();
cache_helper::purge_by_event('changesincoursecat');
- events_trigger('course_category_deleted', $this);
+ // Trigger a course category deleted event.
+ $event = \core\event\course_category_deleted::create(array(
+ 'objectid' => $this->id,
+ 'context' => $coursecatcontext,
+ 'other' => array('name' => $this->name)
+ ));
+ $event->set_legacy_eventdata($this);
+ $event->trigger();
// If we deleted $CFG->defaultrequestcategory, make it point somewhere else.
if ($this->id == $CFG->defaultrequestcategory) {
@@ -1495,6 +1504,7 @@ class coursecat implements renderable, cacheable_object, IteratorAggregate {
*/
public function delete_move($newparentid, $showfeedback = false) {
global $CFG, $DB, $OUTPUT;
+
require_once($CFG->libdir.'/gradelib.php');
require_once($CFG->libdir.'/questionlib.php');
require_once($CFG->dirroot.'/cohort/lib.php');
@@ -1543,9 +1553,15 @@ class coursecat implements renderable, cacheable_object, IteratorAggregate {
// finally delete the category and it's context
$DB->delete_records('course_categories', array('id' => $this->id));
$context->delete();
- add_to_log(SITEID, "category", "delete", "index.php", "$this->name (ID $this->id)");
- events_trigger('course_category_deleted', $this);
+ // Trigger a course category deleted event.
+ $event = \core\event\course_category_deleted::create(array(
+ 'objectid' => $this->id,
+ 'context' => $context,
+ 'other' => array('name' => $this->name)
+ ));
+ $event->set_legacy_eventdata($this);
+ $event->trigger();
cache_helper::purge_by_event('changesincoursecat');
diff --git a/lib/cronlib.php b/lib/cronlib.php
index 0bc2f0cebd5..41c7bb2c604 100644
--- a/lib/cronlib.php
+++ b/lib/cronlib.php
@@ -46,8 +46,7 @@ function cron_run() {
$DB->set_debug(true);
}
if (!empty($CFG->showcrondebugging)) {
- $CFG->debug = DEBUG_DEVELOPER;
- $CFG->debugdisplay = true;
+ set_debugging(DEBUG_DEVELOPER, true);
}
set_time_limit(0);
diff --git a/lib/csslib.php b/lib/csslib.php
index 585ce6343d8..2f9eeb7a43f 100644
--- a/lib/csslib.php
+++ b/lib/csslib.php
@@ -20,7 +20,7 @@
* Please see the {@link css_optimiser} class for greater detail.
*
* @package core
- * @category css
+ * @subpackage cssoptimiser
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@@ -60,7 +60,7 @@ function css_store_css(theme_config $theme, $csspath, array $cssfiles, $chunk =
$css = $optimiser->process($css);
// If cssoptimisestats is set then stats from the optimisation are collected
- // and output at the beginning of the CSS
+ // and output at the beginning of the CSS.
if (!empty($CFG->cssoptimiserstats)) {
$css = $optimiser->output_stats_css().$css;
}
@@ -127,7 +127,7 @@ function css_write_file($filename, $content) {
fclose($fp);
rename($filename.'.tmp', $filename);
@chmod($filename, $CFG->filepermissions);
- @unlink($filename.'.tmp'); // just in case anything fails
+ @unlink($filename.'.tmp'); // Just in case anything fails.
}
}
@@ -221,7 +221,8 @@ function css_chunk_by_selector_count($css, $importurl, $maxselectors = 4095, $bu
* @param string $etag The revision to make sure we utilise any caches.
*/
function css_send_cached_css($csspath, $etag) {
- $lifetime = 60*60*24*60; // 60 days only - the revision may get incremented quite often
+ // 60 days only - the revision may get incremented quite often.
+ $lifetime = 60*60*24*60;
header('Etag: "'.$etag.'"');
header('Content-Disposition: inline; filename="styles.php"');
@@ -251,9 +252,7 @@ function css_send_cached_css($csspath, $etag) {
*
* @param string $css
*/
-function css_send_uncached_css($css, $themesupportsoptimisation = true) {
- global $CFG;
-
+function css_send_uncached_css($css) {
header('Content-Disposition: inline; filename="styles_debug.php"');
header('Last-Modified: '. gmdate('D, d M Y H:i:s', time()) .' GMT');
header('Expires: '. gmdate('D, d M Y H:i:s', time() + THEME_DESIGNER_CACHE_LIFETIME) .' GMT');
@@ -264,19 +263,19 @@ function css_send_uncached_css($css, $themesupportsoptimisation = true) {
if (is_array($css)) {
$css = implode("\n\n", $css);
}
-
echo $css;
-
die;
}
/**
* Send file not modified headers
+ *
* @param int $lastmodified
* @param string $etag
*/
function css_send_unmodified($lastmodified, $etag) {
- $lifetime = 60*60*24*60; // 60 days only - the revision may get incremented quite often
+ // 60 days only - the revision may get incremented quite often.
+ $lifetime = 60*60*24*60;
header('HTTP/1.1 304 Not Modified');
header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT');
header('Cache-Control: public, max-age='.$lifetime);
@@ -327,16 +326,16 @@ function css_is_colour($value) {
} else if (in_array(strtolower($value), array_keys(css_optimiser::$htmlcolours))) {
return true;
} else if (preg_match($rgb, $value, $m) && $m[1] < 256 && $m[2] < 256 && $m[3] < 256) {
- // It is an RGB colour
+ // It is an RGB colour.
return true;
} else if (preg_match($rgba, $value, $m) && $m[1] < 256 && $m[2] < 256 && $m[3] < 256) {
- // It is an RGBA colour
+ // It is an RGBA colour.
return true;
} else if (preg_match($hsl, $value, $m) && $m[1] <= 360 && $m[2] <= 100 && $m[3] <= 100) {
- // It is an HSL colour
+ // It is an HSL colour.
return true;
} else if (preg_match($hsla, $value, $m) && $m[1] <= 360 && $m[2] <= 100 && $m[3] <= 100) {
- // It is an HSLA colour
+ // It is an HSLA colour.
return true;
}
// Doesn't look like a colour.
@@ -379,8 +378,7 @@ function css_sort_by_count(array $a, array $b) {
}
/**
- * A basic CSS optimiser that strips out unwanted things and then processing the
- * CSS organising styles and moving duplicates and useless CSS.
+ * A basic CSS optimiser that strips out unwanted things and then processes CSS organising and cleaning styles.
*
* This CSS optimiser works by reading through a CSS string one character at a
* time and building an object structure of the CSS.
@@ -389,7 +387,7 @@ function css_sort_by_count(array $a, array $b) {
* then combined into an optimised form to keep them as short as possible.
*
* @package core
- * @category css
+ * @subpackage cssoptimiser
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@@ -503,9 +501,7 @@ class css_optimiser {
* @return string The optimised CSS
*/
public function process($css) {
- global $CFG;
-
- // Easiest win there is
+ // Easiest win there is.
$css = trim($css);
$this->reset_stats();
@@ -526,7 +522,7 @@ class css_optimiser {
$css = preg_replace('#\r?\n#', ' ', $css);
// Next remove the comments... no need to them in an optimised world and
- // knowing they're all gone allows us to REALLY make our processing simpler
+ // knowing they're all gone allows us to REALLY make our processing simpler.
$css = preg_replace('#/\*(.*?)\*/#m', '', $css, -1, $this->commentsincss);
$medias = array(
@@ -544,6 +540,7 @@ class css_optimiser {
$inbraces = false; // {
$inbrackets = false; // [
$inparenthesis = false; // (
+ /* @var css_media $currentmedia */
$currentmedia = $medias['all'];
$currentatrule = null;
$suspectatrule = false;
@@ -560,7 +557,7 @@ class css_optimiser {
$suspectatrule = true;
}
switch ($currentprocess) {
- // Start processing an @ rule e.g. @media, @page, @keyframes
+ // Start processing an @ rule e.g. @media, @page, @keyframes.
case self::PROCESSING_ATRULE:
switch ($char) {
case ';':
@@ -578,13 +575,17 @@ class css_optimiser {
$buffer = '';
$currentatrule = false;
}
- // continue 1: The switch processing chars
- // continue 2: The switch processing the state
- // continue 3: The for loop
+ // Continue 1: The switch processing chars
+ // Continue 2: The switch processing the state
+ // Continue 3: The for loop.
continue 3;
case '{':
- if ($currentatrule == 'media' && preg_match('#\s*@media\s*([a-zA-Z0-9]+(\s*,\s*[a-zA-Z0-9]+)*)\s*{#', $buffer, $matches)) {
- // Basic media declaration
+ $regexmediabasic = '#\s*@media\s*([a-zA-Z0-9]+(\s*,\s*[a-zA-Z0-9]+)*)\s*{#';
+ $regexadvmedia = '#\s*@media\s*([^{]+)#';
+ $regexkeyframes = '#@((\-moz\-|\-webkit\-|\-ms\-|\-o\-)?keyframes)\s*([^\s]+)#';
+
+ if ($currentatrule == 'media' && preg_match($regexmediabasic, $buffer, $matches)) {
+ // Basic media declaration.
$mediatypes = str_replace(' ', '', $matches[1]);
if (!array_key_exists($mediatypes, $medias)) {
$medias[$mediatypes] = new css_media($mediatypes);
@@ -592,17 +593,17 @@ class css_optimiser {
$currentmedia = $medias[$mediatypes];
$currentprocess = self::PROCESSING_SELECTORS;
$buffer = '';
- } else if ($currentatrule == 'media' && preg_match('#\s*@media\s*([^{]+)#', $buffer, $matches)) {
- // Advanced media query declaration http://www.w3.org/TR/css3-mediaqueries/
+ } else if ($currentatrule == 'media' && preg_match($regexadvmedia, $buffer, $matches)) {
+ // Advanced media query declaration http://www.w3.org/TR/css3-mediaqueries/.
$mediatypes = $matches[1];
$hash = md5($mediatypes);
$medias[$hash] = new css_media($mediatypes);
$currentmedia = $medias[$hash];
$currentprocess = self::PROCESSING_SELECTORS;
$buffer = '';
- } else if ($currentatrule == 'keyframes' && preg_match('#@((\-moz\-|\-webkit\-)?keyframes)\s*([^\s]+)#', $buffer, $matches)) {
+ } else if ($currentatrule == 'keyframes' && preg_match($regexkeyframes, $buffer, $matches)) {
// Keyframes declaration, we treat it exactly like a @media declaration except we don't allow
- // them to be overridden to ensure we don't mess anything up. (means we keep everything in order)
+ // them to be overridden to ensure we don't mess anything up. (means we keep everything in order).
$keyframefor = $matches[1];
$keyframename = $matches[3];
$keyframe = new css_keyframe($keyframefor, $keyframename);
@@ -611,40 +612,41 @@ class css_optimiser {
$currentprocess = self::PROCESSING_SELECTORS;
$buffer = '';
}
- // continue 1: The switch processing chars
- // continue 2: The switch processing the state
- // continue 3: The for loop
+ // Continue 1: The switch processing chars
+ // Continue 2: The switch processing the state
+ // Continue 3: The for loop.
continue 3;
}
break;
- // Start processing selectors
+ // Start processing selectors.
case self::PROCESSING_START:
case self::PROCESSING_SELECTORS:
+ $regexatrule = '#@(media|import|charset|(\-moz\-|\-webkit\-|\-ms\-|\-o\-)?(keyframes))\s*#';
switch ($char) {
case '[':
$inbrackets ++;
$buffer .= $char;
- // continue 1: The switch processing chars
- // continue 2: The switch processing the state
- // continue 3: The for loop
+ // Continue 1: The switch processing chars
+ // Continue 2: The switch processing the state
+ // Continue 3: The for loop.
continue 3;
case ']':
$inbrackets --;
$buffer .= $char;
- // continue 1: The switch processing chars
- // continue 2: The switch processing the state
- // continue 3: The for loop
+ // Continue 1: The switch processing chars
+ // Continue 2: The switch processing the state
+ // Continue 3: The for loop.
continue 3;
case ' ':
if ($inbrackets) {
- // continue 1: The switch processing chars
- // continue 2: The switch processing the state
- // continue 3: The for loop
+ // Continue 1: The switch processing chars
+ // Continue 2: The switch processing the state
+ // Continue 3: The for loop.
continue 3;
}
if (!empty($buffer)) {
- // Check for known @ rules
- if ($suspectatrule && preg_match('#@(media|import|charset|(\-moz\-|\-webkit\-)?(keyframes))\s*#', $buffer, $matches)) {
+ // Check for known @ rules.
+ if ($suspectatrule && preg_match($regexatrule, $buffer, $matches)) {
$currentatrule = (!empty($matches[3]))?$matches[3]:$matches[1];
$currentprocess = self::PROCESSING_ATRULE;
$buffer .= $char;
@@ -654,15 +656,27 @@ class css_optimiser {
}
}
$suspectatrule = false;
- // continue 1: The switch processing chars
- // continue 2: The switch processing the state
- // continue 3: The for loop
+ // Continue 1: The switch processing chars
+ // Continue 2: The switch processing the state
+ // Continue 3: The for loop.
continue 3;
case '{':
if ($inbrackets) {
- // continue 1: The switch processing chars
- // continue 2: The switch processing the state
- // continue 3: The for loop
+ // Continue 1: The switch processing chars
+ // Continue 2: The switch processing the state
+ // Continue 3: The for loop.
+ continue 3;
+ }
+ // Check for known @ rules.
+ if ($suspectatrule && preg_match($regexatrule, $buffer, $matches)) {
+ // Ahh we've been in an @rule, lets rewind one and have the @rule case process this.
+ $currentatrule = (!empty($matches[3]))?$matches[3]:$matches[1];
+ $currentprocess = self::PROCESSING_ATRULE;
+ $i--;
+ $suspectatrule = false;
+ // Continue 1: The switch processing chars
+ // Continue 2: The switch processing the state
+ // Continue 3: The for loop.
continue 3;
}
if ($buffer !== '') {
@@ -673,15 +687,15 @@ class css_optimiser {
$currentprocess = self::PROCESSING_STYLES;
$buffer = '';
- // continue 1: The switch processing chars
- // continue 2: The switch processing the state
- // continue 3: The for loop
+ // Continue 1: The switch processing chars
+ // Continue 2: The switch processing the state
+ // Continue 3: The for loop.
continue 3;
case '}':
if ($inbrackets) {
- // continue 1: The switch processing chars
- // continue 2: The switch processing the state
- // continue 3: The for loop
+ // Continue 1: The switch processing chars
+ // Continue 2: The switch processing the state
+ // Continue 3: The for loop.
continue 3;
}
if ($currentatrule == 'media') {
@@ -693,28 +707,28 @@ class css_optimiser {
$currentatrule = false;
$buffer = '';
}
- // continue 1: The switch processing chars
- // continue 2: The switch processing the state
- // continue 3: The for loop
+ // Continue 1: The switch processing chars
+ // Continue 2: The switch processing the state
+ // Continue 3: The for loop.
continue 3;
case ',':
if ($inbrackets) {
- // continue 1: The switch processing chars
- // continue 2: The switch processing the state
- // continue 3: The for loop
+ // Continue 1: The switch processing chars
+ // Continue 2: The switch processing the state
+ // Continue 3: The for loop.
continue 3;
}
$currentselector->add($buffer);
$currentrule->add_selector($currentselector);
$currentselector = css_selector::init();
$buffer = '';
- // continue 1: The switch processing chars
- // continue 2: The switch processing the state
- // continue 3: The for loop
+ // Continue 1: The switch processing chars
+ // Continue 2: The switch processing the state
+ // Continue 3: The for loop.
continue 3;
}
break;
- // Start processing styles
+ // Start processing styles.
case self::PROCESSING_STYLES:
if ($char == '"' || $char == "'") {
if ($inquotes === false) {
@@ -732,17 +746,17 @@ class css_optimiser {
case ';':
if ($inparenthesis) {
$buffer .= $char;
- // continue 1: The switch processing chars
- // continue 2: The switch processing the state
- // continue 3: The for loop
+ // Continue 1: The switch processing chars
+ // Continue 2: The switch processing the state
+ // Continue 3: The for loop.
continue 3;
}
$currentrule->add_style($buffer);
$buffer = '';
$inquotes = false;
- // continue 1: The switch processing chars
- // continue 2: The switch processing the state
- // continue 3: The for loop
+ // Continue 1: The switch processing chars
+ // Continue 2: The switch processing the state
+ // Continue 3: The for loop.
continue 3;
case '}':
$currentrule->add_style($buffer);
@@ -756,23 +770,23 @@ class css_optimiser {
$buffer = '';
$inquotes = false;
$inparenthesis = false;
- // continue 1: The switch processing chars
- // continue 2: The switch processing the state
- // continue 3: The for loop
+ // Continue 1: The switch processing chars
+ // Continue 2: The switch processing the state
+ // Continue 3: The for loop.
continue 3;
case '(':
$inparenthesis = true;
$buffer .= $char;
- // continue 1: The switch processing chars
- // continue 2: The switch processing the state
- // continue 3: The for loop
+ // Continue 1: The switch processing chars
+ // Continue 2: The switch processing the state
+ // Continue 3: The for loop.
continue 3;
case ')':
$inparenthesis = false;
$buffer .= $char;
- // continue 1: The switch processing chars
- // continue 2: The switch processing the state
- // continue 3: The for loop
+ // Continue 1: The switch processing chars
+ // Continue 2: The switch processing the state
+ // Continue 3: The for loop.
continue 3;
}
break;
@@ -793,8 +807,8 @@ class css_optimiser {
* Produces CSS for the given charset, imports, media, and keyframes
* @param string $charset
* @param array $imports
- * @param array $medias
- * @param array $keyframes
+ * @param css_media[] $medias
+ * @param css_keyframe[] $keyframes
* @return string
*/
protected function produce_css($charset, array $imports, array $medias, array $keyframes) {
@@ -811,9 +825,9 @@ class css_optimiser {
$cssstandard = array();
$csskeyframes = array();
- // Process each media declaration individually
+ // Process each media declaration individually.
foreach ($medias as $media) {
- // If this declaration applies to all media types
+ // If this declaration applies to all media types.
if (in_array('all', $media->get_types())) {
// Collect all rules that represet reset rules and remove them from the media object at the same time.
// We do this because we prioritise reset rules to the top of a CSS output. This ensures that they
@@ -823,7 +837,7 @@ class css_optimiser {
$cssreset[] = css_writer::media('all', $resetrules);
}
}
- // Get the standard cSS
+ // Get the standard cSS.
$cssstandard[] = $media->out();
}
@@ -839,7 +853,7 @@ class css_optimiser {
}
}
- // Join it all together
+ // Join it all together.
$css .= join('', $cssreset);
$css .= join('', $cssstandard);
$css .= join('', $csskeyframes);
@@ -847,7 +861,7 @@ class css_optimiser {
// Record the strlenght of the now optimised CSS.
$this->optimisedstrlen = strlen($css);
- // Return the now produced CSS
+ // Return the now produced CSS.
return $css;
}
@@ -886,7 +900,7 @@ class css_optimiser {
'improvementrules' => '-',
'improvementselectors' => '-',
);
- // Avoid division by 0 errors by checking we have valid raw values
+ // Avoid division by 0 errors by checking we have valid raw values.
if ($this->rawstrlen > 0) {
$stats['improvementstrlen'] = round(100 - ($this->optimisedstrlen / $this->rawstrlen) * 100, 1).'%';
}
@@ -917,7 +931,7 @@ class css_optimiser {
public function get_errors($clear = false) {
$errors = $this->errors;
if ($clear) {
- // Reset the error array
+ // Reset the error array.
$this->errors = array();
}
return $errors;
@@ -1001,8 +1015,7 @@ class css_optimiser {
* This reference table is used to allow us to unify colours, and will aid
* us in identifying buggy CSS using unsupported colours.
*
- * @staticvar array
- * @var array
+ * @var string[]
*/
public static $htmlcolours = array(
'aliceblue' => '#F0F8FF',
@@ -1160,7 +1173,7 @@ class css_optimiser {
* Used to prepare CSS strings
*
* @package core
- * @category css
+ * @subpackage cssoptimiser
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@@ -1218,7 +1231,7 @@ abstract class css_writer {
* Returns CSS for media
*
* @param string $typestring
- * @param array $rules An array of css_rule objects
+ * @param css_rule[] $rules An array of css_rule objects
* @return string
*/
public static function media($typestring, array &$rules) {
@@ -1244,12 +1257,10 @@ abstract class css_writer {
*
* @param string $for The desired declaration. e.g. keyframes, -moz-keyframes, -webkit-keyframes
* @param string $name The name for the keyframe
- * @param array $rules An array of rules belonging to the keyframe
+ * @param css_rule[] $rules An array of rules belonging to the keyframe
* @return string
*/
public static function keyframe($for, $name, array &$rules) {
- $nl = self::get_separator();
-
$output = "\n@{$for} {$name} {";
foreach ($rules as $rule) {
$output .= $rule->out();
@@ -1273,7 +1284,7 @@ abstract class css_writer {
/**
* Returns CSS for the selectors of a rule
*
- * @param array $selectors Array of css_selector objects
+ * @param css_selector[] $selectors Array of css_selector objects
* @return string
*/
public static function selectors(array $selectors) {
@@ -1298,7 +1309,7 @@ abstract class css_writer {
/**
* Returns a CSS string for the provided styles
*
- * @param array $styles Array of css_style objects
+ * @param css_style[] $styles Array of css_style objects
* @return string
*/
public static function styles(array $styles) {
@@ -1308,6 +1319,7 @@ abstract class css_writer {
// An advanced style is a style with one or more values, and can occur in situations like background-image
// where browse specific values are being used.
if (is_array($style)) {
+ /* @var css_style[] $style */
foreach ($style as $advstyle) {
$bits[] = $advstyle->out();
}
@@ -1335,6 +1347,25 @@ abstract class css_writer {
}
}
+/**
+ * A consolidatable style interface.
+ *
+ * Class that implement this have a short-hand notation for specifying multiple styles.
+ *
+ * @package core
+ * @subpackage cssoptimiser
+ * @copyright 2012 Sam Hemelryk
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+interface core_css_consolidatable_style {
+ /**
+ * Used to consolidate several styles into a single "short-hand" style.
+ * @param array $styles
+ * @return mixed
+ */
+ public static function consolidate(array $styles);
+}
+
/**
* A structure to represent a CSS selector.
*
@@ -1342,7 +1373,7 @@ abstract class css_writer {
* rule.
*
* @package core
- * @category css
+ * @subpackage cssoptimiser
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@@ -1378,7 +1409,9 @@ class css_selector {
/**
* CSS selectors can only be created through the init method above.
*/
- protected function __construct() {}
+ protected function __construct() {
+ // Nothing to do here by default.
+ }
/**
* Adds a selector to the end of the current selector
@@ -1391,13 +1424,13 @@ class css_selector {
if (strpos($selector, '.') !== 0 && strpos($selector, '#') !== 0) {
$count ++;
}
- // If its already false then no need to continue, its not basic
+ // If its already false then no need to continue, its not basic.
if ($this->isbasic !== false) {
- // If theres more than one part making up this selector its not basic
+ // If theres more than one part making up this selector its not basic.
if ($count > 1) {
$this->isbasic = false;
} else {
- // Check whether it is a basic element (a-z+) with possible psuedo selector
+ // Check whether it is a basic element (a-z+) with possible psuedo selector.
$this->isbasic = (bool)preg_match('#^[a-z]+(:[a-zA-Z]+)?$#', $selector);
}
}
@@ -1433,7 +1466,7 @@ class css_selector {
* A structure to represent a CSS rule.
*
* @package core
- * @category css
+ * @subpackage cssoptimiser
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@@ -1441,13 +1474,13 @@ class css_rule {
/**
* An array of CSS selectors {@link css_selector}
- * @var array
+ * @var css_selector[]
*/
protected $selectors = array();
/**
* An array of CSS styles {@link css_style}
- * @var array
+ * @var css_style[]
*/
protected $styles = array();
@@ -1463,7 +1496,7 @@ class css_rule {
* Constructs a new css rule.
*
* @param string $selector The selector or array of selectors that make up this rule.
- * @param array $styles An array of styles that belong to this rule.
+ * @param css_style[] $styles An array of styles that belong to this rule.
*/
protected function __construct($selector = null, array $styles = array()) {
if ($selector != null) {
@@ -1508,7 +1541,7 @@ class css_rule {
} else if ($style instanceof css_style) {
// Clone the style as it may be coming from another rule and we don't
// want references as it will likely be overwritten by proceeding
- // rules
+ // rules.
$style = clone($style);
}
if ($style instanceof css_style) {
@@ -1544,7 +1577,7 @@ class css_rule {
* This method simply iterates over the array and calls {@link css_rule::add_style()}
* with each.
*
- * @param array $styles Adds an array of styles
+ * @param css_style[] $styles Adds an array of styles
*/
public function add_styles(array $styles) {
foreach ($styles as $style) {
@@ -1555,7 +1588,7 @@ class css_rule {
/**
* Returns the array of selectors
*
- * @return array
+ * @return css_selector[]
*/
public function get_selectors() {
return $this->selectors;
@@ -1564,7 +1597,7 @@ class css_rule {
/**
* Returns the array of styles
*
- * @return array
+ * @return css_style[]
*/
public function get_styles() {
return $this->styles;
@@ -1584,12 +1617,16 @@ class css_rule {
/**
* Consolidates all styles associated with this rule
*
- * @return array An array of consolidated styles
+ * @return css_style[] An array of consolidated styles
*/
public function get_consolidated_styles() {
+ /* @var css_style[] $organisedstyles */
$organisedstyles = array();
+ /* @var css_style[] $finalstyles */
$finalstyles = array();
+ /* @var core_css_consolidatable_style[] $consolidate */
$consolidate = array();
+ /* @var css_style[] $advancedstyles */
$advancedstyles = array();
foreach ($this->styles as $style) {
// If the style is an array then we are processing an advanced style. An advanced style is a style that can have
@@ -1598,6 +1635,7 @@ class css_rule {
$single = null;
$count = 0;
foreach ($style as $advstyle) {
+ /* @var css_style $advstyle */
$key = $count++;
$advancedstyles[$key] = $advstyle;
if (!$advstyle->allows_multiple_values()) {
@@ -1611,7 +1649,8 @@ class css_rule {
$style = $advancedstyles[$single];
$consolidatetoclass = $style->consolidate_to();
- if (($style->is_valid() || $style->is_special_empty_value()) && !empty($consolidatetoclass) && class_exists('css_style_'.$consolidatetoclass)) {
+ if (($style->is_valid() || $style->is_special_empty_value()) && !empty($consolidatetoclass) &&
+ class_exists('css_style_'.$consolidatetoclass)) {
$class = 'css_style_'.$consolidatetoclass;
if (!array_key_exists($class, $consolidate)) {
$consolidate[$class] = array();
@@ -1625,7 +1664,8 @@ class css_rule {
continue;
}
$consolidatetoclass = $style->consolidate_to();
- if (($style->is_valid() || $style->is_special_empty_value()) && !empty($consolidatetoclass) && class_exists('css_style_'.$consolidatetoclass)) {
+ if (($style->is_valid() || $style->is_special_empty_value()) && !empty($consolidatetoclass) &&
+ class_exists('css_style_'.$consolidatetoclass)) {
$class = 'css_style_'.$consolidatetoclass;
if (!array_key_exists($class, $consolidate)) {
$consolidate[$class] = array();
@@ -1638,7 +1678,7 @@ class css_rule {
}
foreach ($consolidate as $class => $styles) {
- $organisedstyles[$class] = $class::consolidate($styles);
+ $organisedstyles[$class] = call_user_func(array($class, 'consolidate'), $styles);
}
foreach ($organisedstyles as $style) {
@@ -1658,7 +1698,7 @@ class css_rule {
* Splits this rules into an array of CSS rules. One for each of the selectors
* that make up this rule.
*
- * @return array(css_rule)
+ * @return css_rule[]
*/
public function split_by_selector() {
$return = array();
@@ -1672,7 +1712,7 @@ class css_rule {
* Splits this rule into an array of rules. One for each of the styles that
* make up this rule
*
- * @return array Array of css_rule objects
+ * @return css_rule[] Array of css_rule objects
*/
public function split_by_style() {
$return = array();
@@ -1725,6 +1765,7 @@ class css_rule {
public function has_errors() {
foreach ($this->styles as $style) {
if (is_array($style)) {
+ /* @var css_style[] $style */
foreach ($style as $advstyle) {
if ($advstyle->has_error()) {
return true;
@@ -1752,9 +1793,10 @@ class css_rule {
$errors = array();
foreach ($this->styles as $style) {
if (is_array($style)) {
- foreach ($style as $s) {
- if ($style instanceof css_style && $style->has_error()) {
- $errors[] = " * ".$style->get_last_error();
+ /* @var css_style[] $style */
+ foreach ($style as $advstyle) {
+ if ($advstyle instanceof css_style && $advstyle->has_error()) {
+ $errors[] = " * ".$advstyle->get_last_error();
}
}
} else if ($style instanceof css_style && $style->has_error()) {
@@ -1789,14 +1831,14 @@ class css_rule {
* When no declaration is specified rules accumulate into @media all.
*
* @package core
- * @category css
+ * @subpackage cssoptimiser
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class css_rule_collection {
/**
* An array of rules within this collection instance
- * @var array
+ * @var css_rule[]
*/
protected $rules = array();
@@ -1824,7 +1866,7 @@ abstract class css_rule_collection {
/**
* Returns the rules used by this collection
*
- * @return array
+ * @return css_rule[]
*/
public function get_rules() {
return $this->rules;
@@ -1837,9 +1879,11 @@ abstract class css_rule_collection {
* @return bool True if the CSS was optimised by this method
*/
public function organise_rules_by_selectors() {
- $optimised = array();
+ /* @var css_rule[] $optimisedrules */
+ $optimisedrules = array();
$beforecount = count($this->rules);
$lasthash = null;
+ /* @var css_rule $lastrule */
$lastrule = null;
foreach ($this->rules as $rule) {
$hash = $rule->get_style_hash();
@@ -1851,10 +1895,10 @@ abstract class css_rule_collection {
}
$lastrule = clone($rule);
$lasthash = $hash;
- $optimised[] = $lastrule;
+ $optimisedrules[] = $lastrule;
}
$this->rules = array();
- foreach ($optimised as $optimised) {
+ foreach ($optimisedrules as $optimised) {
$this->rules[$optimised->get_selector_hash()] = $optimised;
}
$aftercount = count($this->rules);
@@ -1900,7 +1944,7 @@ abstract class css_rule_collection {
/**
* Returns any errors that have happened within rules in this collection.
*
- * @return string
+ * @return string[]
*/
public function get_errors() {
$errors = array();
@@ -1917,7 +1961,7 @@ abstract class css_rule_collection {
* A media class to organise rules by the media they apply to.
*
* @package core
- * @category css
+ * @subpackage cssoptimiser
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@@ -1980,16 +2024,22 @@ class css_media extends css_rule_collection {
* A media class to organise rules by the media they apply to.
*
* @package core
- * @category css
+ * @subpackage cssoptimiser
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class css_keyframe extends css_rule_collection {
- /** @var string $for The directive e.g. keyframes, -moz-keyframes, -webkit-keyframes */
+ /**
+ * The directive e.g. keyframes, -moz-keyframes, -webkit-keyframes
+ * @var string
+ */
protected $for;
- /** @var string $name The name for the keyframes */
+ /**
+ * The name for the keyframes
+ * @var string
+ */
protected $name;
/**
* Constructs a new keyframe
@@ -2031,7 +2081,7 @@ class css_keyframe extends css_rule_collection {
* An absract class to represent CSS styles
*
* @package core
- * @category css
+ * @subpackage cssoptimiser
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@@ -2085,9 +2135,14 @@ abstract class css_style {
* @return css_style_generic
*/
public static function init_automatic($name, $value) {
- $specificclass = 'css_style_'.preg_replace('#[^a-zA-Z0-9]+#', '', $name);
+ $cleanedname = preg_replace('#[^a-zA-Z0-9]+#', '', $name);
+ $specificclass = 'css_style_'.$cleanedname;
if (class_exists($specificclass)) {
- return $specificclass::init($value);
+ $style = call_user_func(array($specificclass, 'init'), $value);
+ if ($cleanedname !== $name && !is_array($style)) {
+ $style->set_actual_name($name);
+ }
+ return $style;
}
return new css_style_generic($name, $value);
}
@@ -2263,13 +2318,24 @@ abstract class css_style {
public function set_important($important = true) {
$this->important = (bool) $important;
}
+
+ /**
+ * Sets the actual name used within the style.
+ *
+ * This method allows us to support browser hacks like *width:0;
+ *
+ * @param string $name
+ */
+ public function set_actual_name($name) {
+ $this->name = $name;
+ }
}
/**
* A generic CSS style class to use when a more specific class does not exist.
*
* @package core
- * @category css
+ * @subpackage cssoptimiser
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@@ -2295,7 +2361,7 @@ class css_style_generic extends css_style {
* A colour CSS style
*
* @package core
- * @category css
+ * @subpackage cssoptimiser
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@@ -2380,7 +2446,7 @@ class css_style_color extends css_style {
* A width style
*
* @package core
- * @category css
+ * @subpackage cssoptimiser
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@@ -2426,11 +2492,11 @@ class css_style_width extends css_style {
* A margin style
*
* @package core
- * @category css
+ * @subpackage cssoptimiser
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-class css_style_margin extends css_style_width {
+class css_style_margin extends css_style_width implements core_css_consolidatable_style {
/**
* Initialises a margin style.
@@ -2476,8 +2542,8 @@ class css_style_margin extends css_style_width {
/**
* Consolidates individual margin styles into a single margin style
*
- * @param array $styles
- * @return array An array of consolidated styles
+ * @param css_style[] $styles
+ * @return css_style[] An array of consolidated styles
*/
public static function consolidate(array $styles) {
if (count($styles) != 4) {
@@ -2565,7 +2631,7 @@ class css_style_margin extends css_style_width {
* A margin top style
*
* @package core
- * @category css
+ * @subpackage cssoptimiser
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@@ -2595,7 +2661,7 @@ class css_style_margintop extends css_style_margin {
* A margin right style
*
* @package core
- * @category css
+ * @subpackage cssoptimiser
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@@ -2625,7 +2691,7 @@ class css_style_marginright extends css_style_margin {
* A margin bottom style
*
* @package core
- * @category css
+ * @subpackage cssoptimiser
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@@ -2655,7 +2721,7 @@ class css_style_marginbottom extends css_style_margin {
* A margin left style
*
* @package core
- * @category css
+ * @subpackage cssoptimiser
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@@ -2685,11 +2751,11 @@ class css_style_marginleft extends css_style_margin {
* A border style
*
* @package core
- * @category css
+ * @subpackage cssoptimiser
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-class css_style_border extends css_style {
+class css_style_border extends css_style implements core_css_consolidatable_style {
/**
* Initalises the border style into an array of individual style compontents
@@ -2707,24 +2773,24 @@ class css_style_border extends css_style {
if (!css_style_borderwidth::is_border_width($width)) {
$width = '0';
}
- $return[] = new css_style_borderwidth('border-top-width', $width);
- $return[] = new css_style_borderwidth('border-right-width', $width);
- $return[] = new css_style_borderwidth('border-bottom-width', $width);
- $return[] = new css_style_borderwidth('border-left-width', $width);
+ $return[] = css_style_bordertopwidth::init($width);
+ $return[] = css_style_borderrightwidth::init($width);
+ $return[] = css_style_borderbottomwidth::init($width);
+ $return[] = css_style_borderleftwidth::init($width);
}
if (count($bits) > 0) {
$style = array_shift($bits);
- $return[] = new css_style_borderstyle('border-top-style', $style);
- $return[] = new css_style_borderstyle('border-right-style', $style);
- $return[] = new css_style_borderstyle('border-bottom-style', $style);
- $return[] = new css_style_borderstyle('border-left-style', $style);
+ $return[] = css_style_bordertopstyle::init($style);
+ $return[] = css_style_borderrightstyle::init($style);
+ $return[] = css_style_borderbottomstyle::init($style);
+ $return[] = css_style_borderleftstyle::init($style);
}
if (count($bits) > 0) {
$colour = array_shift($bits);
- $return[] = new css_style_bordercolor('border-top-color', $colour);
- $return[] = new css_style_bordercolor('border-right-color', $colour);
- $return[] = new css_style_bordercolor('border-bottom-color', $colour);
- $return[] = new css_style_bordercolor('border-left-color', $colour);
+ $return[] = css_style_bordertopcolor::init($colour);
+ $return[] = css_style_borderrightcolor::init($colour);
+ $return[] = css_style_borderbottomcolor::init($colour);
+ $return[] = css_style_borderleftcolor::init($colour);
}
return $return;
}
@@ -2732,8 +2798,8 @@ class css_style_border extends css_style {
/**
* Consolidates all border styles into a single style
*
- * @param array $styles An array of border styles
- * @return array An optimised array of border styles
+ * @param css_style[] $styles An array of border styles
+ * @return css_style[] An optimised array of border styles
*/
public static function consolidate(array $styles) {
@@ -2800,9 +2866,10 @@ class css_style_border extends css_style {
$allstylesnull = $allstylesthesame && $nullstyles;
$allcolorsnull = $allcolorsthesame && $nullcolors;
+ /* @var css_style[] $return */
$return = array();
if ($allwidthsnull && $allstylesnull && $allcolorsnull) {
- // Everything is null still... boo
+ // Everything is null still... boo.
return array(new css_style_border('border', ''));
} else if ($allwidthsnull && $allstylesnull) {
@@ -2849,7 +2916,11 @@ class css_style_border extends css_style {
self::consolidate_styles_by_direction($return, 'css_style_bordercolor', 'border-color', $bordercolors);
}
- } else if (!$nullwidths && !$nullcolors && !$nullstyles && max(array_count_values($borderwidths)) == 3 && max(array_count_values($borderstyles)) == 3 && max(array_count_values($bordercolors)) == 3) {
+ } else if (!$nullwidths && !$nullcolors && !$nullstyles &&
+ max(array_count_values($borderwidths)) == 3 &&
+ max(array_count_values($borderstyles)) == 3 &&
+ max(array_count_values($bordercolors)) == 3) {
+
$widthkeys = array();
$stylekeys = array();
$colorkeys = array();
@@ -2883,20 +2954,30 @@ class css_style_border extends css_style {
if ($widthkeys == $stylekeys && $stylekeys == $colorkeys) {
$key = $widthkeys[0][0];
- self::build_style_string($return, 'css_style_border', 'border', $borderwidths[$key], $borderstyles[$key], $bordercolors[$key]);
+ self::build_style_string($return, 'css_style_border', 'border',
+ $borderwidths[$key], $borderstyles[$key], $bordercolors[$key]);
$key = $widthkeys[1][0];
- self::build_style_string($return, 'css_style_border'.$key, 'border-'.$key, $borderwidths[$key], $borderstyles[$key], $bordercolors[$key]);
+ self::build_style_string($return, 'css_style_border'.$key, 'border-'.$key,
+ $borderwidths[$key], $borderstyles[$key], $bordercolors[$key]);
} else {
- self::build_style_string($return, 'css_style_bordertop', 'border-top', $borderwidths['top'], $borderstyles['top'], $bordercolors['top']);
- self::build_style_string($return, 'css_style_borderright', 'border-right', $borderwidths['right'], $borderstyles['right'], $bordercolors['right']);
- self::build_style_string($return, 'css_style_borderbottom', 'border-bottom', $borderwidths['bottom'], $borderstyles['bottom'], $bordercolors['bottom']);
- self::build_style_string($return, 'css_style_borderleft', 'border-left', $borderwidths['left'], $borderstyles['left'], $bordercolors['left']);
+ self::build_style_string($return, 'css_style_bordertop', 'border-top',
+ $borderwidths['top'], $borderstyles['top'], $bordercolors['top']);
+ self::build_style_string($return, 'css_style_borderright', 'border-right',
+ $borderwidths['right'], $borderstyles['right'], $bordercolors['right']);
+ self::build_style_string($return, 'css_style_borderbottom', 'border-bottom',
+ $borderwidths['bottom'], $borderstyles['bottom'], $bordercolors['bottom']);
+ self::build_style_string($return, 'css_style_borderleft', 'border-left',
+ $borderwidths['left'], $borderstyles['left'], $bordercolors['left']);
}
} else {
- self::build_style_string($return, 'css_style_bordertop', 'border-top', $borderwidths['top'], $borderstyles['top'], $bordercolors['top']);
- self::build_style_string($return, 'css_style_borderright', 'border-right', $borderwidths['right'], $borderstyles['right'], $bordercolors['right']);
- self::build_style_string($return, 'css_style_borderbottom', 'border-bottom', $borderwidths['bottom'], $borderstyles['bottom'], $bordercolors['bottom']);
- self::build_style_string($return, 'css_style_borderleft', 'border-left', $borderwidths['left'], $borderstyles['left'], $bordercolors['left']);
+ self::build_style_string($return, 'css_style_bordertop', 'border-top',
+ $borderwidths['top'], $borderstyles['top'], $bordercolors['top']);
+ self::build_style_string($return, 'css_style_borderright', 'border-right',
+ $borderwidths['right'], $borderstyles['right'], $bordercolors['right']);
+ self::build_style_string($return, 'css_style_borderbottom', 'border-bottom',
+ $borderwidths['bottom'], $borderstyles['bottom'], $bordercolors['bottom']);
+ self::build_style_string($return, 'css_style_borderleft', 'border-left',
+ $borderwidths['left'], $borderstyles['left'], $bordercolors['left']);
}
foreach ($return as $key => $style) {
if ($style->get_value() == '') {
@@ -2929,7 +3010,8 @@ class css_style_border extends css_style {
* @param string $left The left value
* @return bool
*/
- public static function consolidate_styles_by_direction(&$array, $class, $style, $top, $right = null, $bottom = null, $left = null) {
+ public static function consolidate_styles_by_direction(&$array, $class, $style,
+ $top, $right = null, $bottom = null, $left = null) {
if (is_array($top)) {
$right = $top['right'];
$bottom = $top['bottom'];
@@ -3003,7 +3085,7 @@ class css_style_border extends css_style {
* A border colour style
*
* @package core
- * @category css
+ * @subpackage cssoptimiser
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@@ -3083,7 +3165,7 @@ class css_style_bordercolor extends css_style_color {
* A border left style
*
* @package core
- * @category css
+ * @subpackage cssoptimiser
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@@ -3126,7 +3208,7 @@ class css_style_borderleft extends css_style_generic {
* A border right style
*
* @package core
- * @category css
+ * @subpackage cssoptimiser
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@@ -3169,7 +3251,7 @@ class css_style_borderright extends css_style_generic {
* A border top style
*
* @package core
- * @category css
+ * @subpackage cssoptimiser
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@@ -3212,7 +3294,7 @@ class css_style_bordertop extends css_style_generic {
* A border bottom style
*
* @package core
- * @category css
+ * @subpackage cssoptimiser
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@@ -3255,7 +3337,7 @@ class css_style_borderbottom extends css_style_generic {
* A border width style
*
* @package core
- * @category css
+ * @subpackage cssoptimiser
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@@ -3342,7 +3424,7 @@ class css_style_borderwidth extends css_style_width {
* A border style style
*
* @package core
- * @category css
+ * @subpackage cssoptimiser
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@@ -3395,7 +3477,7 @@ class css_style_borderstyle extends css_style_generic {
* A border top colour style
*
* @package core
- * @category css
+ * @subpackage cssoptimiser
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@@ -3425,7 +3507,7 @@ class css_style_bordertopcolor extends css_style_bordercolor {
* A border left colour style
*
* @package core
- * @category css
+ * @subpackage cssoptimiser
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@@ -3455,7 +3537,7 @@ class css_style_borderleftcolor extends css_style_bordercolor {
* A border right colour style
*
* @package core
- * @category css
+ * @subpackage cssoptimiser
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@@ -3485,7 +3567,7 @@ class css_style_borderrightcolor extends css_style_bordercolor {
* A border bottom colour style
*
* @package core
- * @category css
+ * @subpackage cssoptimiser
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@@ -3515,7 +3597,7 @@ class css_style_borderbottomcolor extends css_style_bordercolor {
* A border width top style
*
* @package core
- * @category css
+ * @subpackage cssoptimiser
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@@ -3545,7 +3627,7 @@ class css_style_bordertopwidth extends css_style_borderwidth {
* A border width left style
*
* @package core
- * @category css
+ * @subpackage cssoptimiser
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@@ -3575,7 +3657,7 @@ class css_style_borderleftwidth extends css_style_borderwidth {
* A border width right style
*
* @package core
- * @category css
+ * @subpackage cssoptimiser
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@@ -3605,7 +3687,7 @@ class css_style_borderrightwidth extends css_style_borderwidth {
* A border width bottom style
*
* @package core
- * @category css
+ * @subpackage cssoptimiser
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@@ -3635,7 +3717,7 @@ class css_style_borderbottomwidth extends css_style_borderwidth {
* A border top style
*
* @package core
- * @category css
+ * @subpackage cssoptimiser
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@@ -3665,7 +3747,7 @@ class css_style_bordertopstyle extends css_style_borderstyle {
* A border left style
*
* @package core
- * @category css
+ * @subpackage cssoptimiser
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@@ -3695,7 +3777,7 @@ class css_style_borderleftstyle extends css_style_borderstyle {
* A border right style
*
* @package core
- * @category css
+ * @subpackage cssoptimiser
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@@ -3725,7 +3807,7 @@ class css_style_borderrightstyle extends css_style_borderstyle {
* A border bottom style
*
* @package core
- * @category css
+ * @subpackage cssoptimiser
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@@ -3755,11 +3837,11 @@ class css_style_borderbottomstyle extends css_style_borderstyle {
* A background style
*
* @package core
- * @category css
+ * @subpackage cssoptimiser
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-class css_style_background extends css_style {
+class css_style_background extends css_style implements core_css_consolidatable_style {
/**
* Initialises a background style
@@ -3768,15 +3850,14 @@ class css_style_background extends css_style {
* @return array An array of background component.
*/
public static function init($value) {
- // colour - image - repeat - attachment - position
-
+ // Colour - image - repeat - attachment - position.
$imageurl = null;
if (preg_match('#url\(([^\)]+)\)#', $value, $matches)) {
$imageurl = trim($matches[1]);
$value = str_replace($matches[1], '', $value);
}
- // Switch out the brackets so that they don't get messed up when we explode
+ // Switch out the brackets so that they don't get messed up when we explode.
$brackets = array();
$bracketcount = 0;
while (preg_match('#\([^\)\(]+\)#', $value, $matches)) {
@@ -3788,7 +3869,7 @@ class css_style_background extends css_style {
$important = (stripos($value, '!important') !== false);
if ($important) {
- // Great some genius put !important in the background shorthand property
+ // Great some genius put !important in the background shorthand property.
$value = str_replace('!important', '', $value);
}
@@ -3805,6 +3886,7 @@ class css_style_background extends css_style {
$attachments = array('scroll' , 'fixed', 'inherit');
$positions = array('top', 'left', 'bottom', 'right', 'center');
+ /* @var css_style_background[] $return */
$return = array();
$unknownbits = array();
@@ -3828,7 +3910,7 @@ class css_style_background extends css_style {
$attachment = self::NULL_VALUE;
if (count($bits) > 0 && in_array(reset($bits), $attachments)) {
- // scroll , fixed, inherit
+ // Scroll , fixed, inherit.
$attachment = array_shift($bits);
}
@@ -3866,16 +3948,19 @@ class css_style_background extends css_style {
}
}
- if ($color === self::NULL_VALUE && $image === self::NULL_VALUE && $repeat === self::NULL_VALUE && $attachment === self::NULL_VALUE && $position === self::NULL_VALUE) {
+ if ($color === self::NULL_VALUE &&
+ $image === self::NULL_VALUE &&
+ $repeat === self::NULL_VALUE && $attachment === self::NULL_VALUE &&
+ $position === self::NULL_VALUE) {
// All primaries are null, return without doing anything else. There may be advanced madness there.
return $return;
}
- $return[] = new css_style_backgroundcolor('background-color', $color);
- $return[] = new css_style_backgroundimage('background-image', $image);
- $return[] = new css_style_backgroundrepeat('background-repeat', $repeat);
- $return[] = new css_style_backgroundattachment('background-attachment', $attachment);
- $return[] = new css_style_backgroundposition('background-position', $position);
+ $return[] = css_style_backgroundcolor::init($color);
+ $return[] = css_style_backgroundimage::init($image);
+ $return[] = css_style_backgroundrepeat::init($repeat);
+ $return[] = css_style_backgroundattachment::init($attachment);
+ $return[] = css_style_backgroundposition::init($position);
if ($important) {
foreach ($return as $style) {
@@ -3903,8 +3988,8 @@ class css_style_background extends css_style {
/**
* Consolidates background styles into a single background style
*
- * @param array $styles Consolidates the provided array of background styles
- * @return array Consolidated optimised background styles
+ * @param css_style_background[] $styles Consolidates the provided array of background styles
+ * @return css_style[] Consolidated optimised background styles
*/
public static function consolidate(array $styles) {
@@ -3937,8 +4022,11 @@ class css_style_background extends css_style {
}
}
+ /* @var css_style[] $organisedstyles */
$organisedstyles = array();
+ /* @var css_style[] $advancedstyles */
$advancedstyles = array();
+ /* @var css_style[] $importantstyles */
$importantstyles = array();
foreach ($styles as $style) {
if ($style instanceof css_style_backgroundimage_advanced) {
@@ -3978,6 +4066,7 @@ class css_style_background extends css_style {
}
}
+ /* @var css_style[] $consolidatetosingle */
$consolidatetosingle = array();
if (!is_null($color) && !is_null($image) && !is_null($repeat) && !is_null($attachment) && !is_null($position)) {
// We can use the shorthand background-style!
@@ -4005,7 +4094,7 @@ class css_style_background extends css_style {
}
$return = array();
- // Single background style needs to come first;
+ // Single background style needs to come first.
if (count($consolidatetosingle) > 0) {
$returnstyle = new css_style_background('background', join(' ', $consolidatetosingle));
if ($allimportant) {
@@ -4016,14 +4105,30 @@ class css_style_background extends css_style {
foreach ($styles as $style) {
$value = null;
switch ($style->get_name()) {
- case 'background-color' : $value = $color; break;
- case 'background-image' : $value = $image; break;
- case 'background-repeat' : $value = $repeat; break;
- case 'background-attachment' : $value = $attachment; break;
- case 'background-position' : $value = $position; break;
- case 'background-clip' : $value = $clip; break;
- case 'background-origin' : $value = $origin; break;
- case 'background-size' : $value = $size; break;
+ case 'background-color' :
+ $value = $color;
+ break;
+ case 'background-image' :
+ $value = $image;
+ break;
+ case 'background-repeat' :
+ $value = $repeat;
+ break;
+ case 'background-attachment' :
+ $value = $attachment;
+ break;
+ case 'background-position' :
+ $value = $position;
+ break;
+ case 'background-clip' :
+ $value = $clip;
+ break;
+ case 'background-origin':
+ $value = $origin;
+ break;
+ case 'background-size':
+ $value = $size;
+ break;
}
if (!is_null($value)) {
$return[] = $style;
@@ -4038,7 +4143,7 @@ class css_style_background extends css_style {
* A advanced background style that allows multiple values to preserve unknown entities
*
* @package core
- * @category css
+ * @subpackage cssoptimiser
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@@ -4071,7 +4176,7 @@ class css_style_background_advanced extends css_style_generic {
* Based upon the colour style.
*
* @package core
- * @category css
+ * @subpackage cssoptimiser
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@@ -4122,7 +4227,7 @@ class css_style_backgroundcolor extends css_style_color {
* A background image style.
*
* @package core
- * @category css
+ * @subpackage cssoptimiser
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@@ -4135,7 +4240,7 @@ class css_style_backgroundimage extends css_style_generic {
* @return css_style_backgroundimage
*/
public static function init($value) {
- if (!preg_match('#^\s*(none|inherit|url\()#i', $value)) {
+ if ($value !== self::NULL_VALUE && !preg_match('#^\s*(none|inherit|url\()#i', $value)) {
return css_style_backgroundimage_advanced::init($value);
}
return new css_style_backgroundimage('background-image', $value);
@@ -4173,10 +4278,10 @@ class css_style_backgroundimage extends css_style_generic {
}
/**
- * A background image style that supports mulitple values and masquerades as a background-image
+ * A background image style that supports multiple values and masquerades as a background-image
*
* @package core
- * @category css
+ * @subpackage cssoptimiser
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@@ -4207,7 +4312,7 @@ class css_style_backgroundimage_advanced extends css_style_generic {
* A background repeat style.
*
* @package core
- * @category css
+ * @subpackage cssoptimiser
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@@ -4258,7 +4363,7 @@ class css_style_backgroundrepeat extends css_style_generic {
* A background attachment style.
*
* @package core
- * @category css
+ * @subpackage cssoptimiser
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@@ -4309,7 +4414,7 @@ class css_style_backgroundattachment extends css_style_generic {
* A background position style.
*
* @package core
- * @category css
+ * @subpackage cssoptimiser
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@@ -4360,7 +4465,7 @@ class css_style_backgroundposition extends css_style_generic {
* A background size style.
*
* @package core
- * @category css
+ * @subpackage cssoptimiser
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@@ -4390,7 +4495,7 @@ class css_style_backgroundsize extends css_style_generic {
* A background clip style.
*
* @package core
- * @category css
+ * @subpackage cssoptimiser
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@@ -4420,7 +4525,7 @@ class css_style_backgroundclip extends css_style_generic {
* A background origin style.
*
* @package core
- * @category css
+ * @subpackage cssoptimiser
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@@ -4450,11 +4555,11 @@ class css_style_backgroundorigin extends css_style_generic {
* A padding style.
*
* @package core
- * @category css
+ * @subpackage cssoptimiser
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-class css_style_padding extends css_style_width {
+class css_style_padding extends css_style_width implements core_css_consolidatable_style {
/**
* Initialises this padding style into several individual padding styles
@@ -4496,8 +4601,8 @@ class css_style_padding extends css_style_width {
/**
* Consolidates several padding styles into a single style.
*
- * @param array $styles Array of padding styles
- * @return array Optimised+consolidated array of padding styles
+ * @param css_style_padding[] $styles Array of padding styles
+ * @return css_style[] Optimised+consolidated array of padding styles
*/
public static function consolidate(array $styles) {
if (count($styles) != 4) {
@@ -4548,10 +4653,18 @@ class css_style_padding extends css_style_width {
$left = null;
foreach ($styles as $style) {
switch ($style->get_name()) {
- case 'padding-top' : $top = $style->get_value(false);break;
- case 'padding-right' : $right = $style->get_value(false);break;
- case 'padding-bottom' : $bottom = $style->get_value(false);break;
- case 'padding-left' : $left = $style->get_value(false);break;
+ case 'padding-top' :
+ $top = $style->get_value(false);
+ break;
+ case 'padding-right' :
+ $right = $style->get_value(false);
+ break;
+ case 'padding-bottom' :
+ $bottom = $style->get_value(false);
+ break;
+ case 'padding-left' :
+ $left = $style->get_value(false);
+ break;
}
}
if ($top == $bottom && $left == $right) {
@@ -4577,7 +4690,7 @@ class css_style_padding extends css_style_width {
* A padding top style.
*
* @package core
- * @category css
+ * @subpackage cssoptimiser
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@@ -4607,7 +4720,7 @@ class css_style_paddingtop extends css_style_padding {
* A padding right style.
*
* @package core
- * @category css
+ * @subpackage cssoptimiser
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@@ -4637,7 +4750,7 @@ class css_style_paddingright extends css_style_padding {
* A padding bottom style.
*
* @package core
- * @category css
+ * @subpackage cssoptimiser
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@@ -4667,7 +4780,7 @@ class css_style_paddingbottom extends css_style_padding {
* A padding left style.
*
* @package core
- * @category css
+ * @subpackage cssoptimiser
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@@ -4697,7 +4810,7 @@ class css_style_paddingleft extends css_style_padding {
* A cursor style.
*
* @package core
- * @category css
+ * @subpackage cssoptimiser
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@@ -4717,10 +4830,10 @@ class css_style_cursor extends css_style_generic {
* @return string
*/
protected function clean_value($value) {
- // Allowed values for the cursor style
+ // Allowed values for the cursor style.
$allowed = array('auto', 'crosshair', 'default', 'e-resize', 'help', 'move', 'n-resize', 'ne-resize', 'nw-resize',
'pointer', 'progress', 's-resize', 'se-resize', 'sw-resize', 'text', 'w-resize', 'wait', 'inherit');
- // Has to be one of the allowed values of an image to use. Loosely match the image... doesn't need to be thorough
+ // Has to be one of the allowed values of an image to use. Loosely match the image... doesn't need to be thorough.
if (!in_array($value, $allowed) && !preg_match('#\.[a-zA-Z0-9_\-]{1,5}$#', $value)) {
$this->set_error('Invalid or unexpected cursor value specified: '.$value);
}
@@ -4732,7 +4845,7 @@ class css_style_cursor extends css_style_generic {
* A vertical alignment style.
*
* @package core
- * @category css
+ * @subpackage cssoptimiser
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@@ -4764,7 +4877,7 @@ class css_style_verticalalign extends css_style_generic {
* A float style.
*
* @package core
- * @category css
+ * @subpackage cssoptimiser
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
diff --git a/lib/dml/mssql_native_moodle_database.php b/lib/dml/mssql_native_moodle_database.php
index 2191b893f7d..4000e8efd04 100644
--- a/lib/dml/mssql_native_moodle_database.php
+++ b/lib/dml/mssql_native_moodle_database.php
@@ -1220,7 +1220,7 @@ class mssql_native_moodle_database extends moodle_database {
}
public function sql_order_by_text($fieldname, $numchars=32) {
- return ' CONVERT(varchar, ' . $fieldname . ', ' . $numchars . ')';
+ return " CONVERT(varchar({$numchars}), {$fieldname})";
}
/**
diff --git a/lib/dml/pdo_moodle_database.php b/lib/dml/pdo_moodle_database.php
index cc7b3bb0e43..58874bd016b 100644
--- a/lib/dml/pdo_moodle_database.php
+++ b/lib/dml/pdo_moodle_database.php
@@ -308,16 +308,17 @@ abstract class pdo_moodle_database extends moodle_database {
* @return array of objects, or empty array if no records were found, or false if an error occurred.
*/
public function get_records_sql($sql, array $params=null, $limitfrom=0, $limitnum=0) {
+ global $CFG;
+
$rs = $this->get_recordset_sql($sql, $params, $limitfrom, $limitnum);
if (!$rs->valid()) {
$rs->close(); // Not going to iterate (but exit), close rs
return false;
}
$objects = array();
- $debugging = debugging('', DEBUG_DEVELOPER);
foreach($rs as $value) {
$key = reset($value);
- if ($debugging && array_key_exists($key, $objects)) {
+ if ($CFG->debugdeveloper && array_key_exists($key, $objects)) {
debugging("Did you remember to make the first column something unique in your call to get_records? Duplicate value '$key' found in column first column of '$sql'.", DEBUG_DEVELOPER);
}
$objects[$key] = (object)$value;
diff --git a/lib/dml/sqlsrv_native_moodle_database.php b/lib/dml/sqlsrv_native_moodle_database.php
index b5062b120b9..5761c7be6c6 100644
--- a/lib/dml/sqlsrv_native_moodle_database.php
+++ b/lib/dml/sqlsrv_native_moodle_database.php
@@ -1281,7 +1281,7 @@ class sqlsrv_native_moodle_database extends moodle_database {
}
public function sql_order_by_text($fieldname, $numchars = 32) {
- return ' CONVERT(varchar, '.$fieldname.', '.$numchars.')';
+ return " CONVERT(varchar({$numchars}), {$fieldname})";
}
/**
diff --git a/lib/dml/tests/dml_test.php b/lib/dml/tests/dml_test.php
index c01fedb10f0..07ee27e7d75 100644
--- a/lib/dml/tests/dml_test.php
+++ b/lib/dml/tests/dml_test.php
@@ -1475,8 +1475,6 @@ class core_dml_testcase extends database_driver_testcase {
}
public function test_get_records_sql() {
- global $CFG;
-
$DB = $this->tdb;
$dbman = $DB->get_manager();
@@ -1518,11 +1516,11 @@ class core_dml_testcase extends database_driver_testcase {
$records = $DB->get_records_sql("SELECT course AS id, course AS course FROM {{$tablename}}", null);
$this->assertDebuggingCalled();
$this->assertEquals(6, count($records));
- $CFG->debug = DEBUG_MINIMAL;
+ set_debugging(DEBUG_MINIMAL);
$records = $DB->get_records_sql("SELECT course AS id, course AS course FROM {{$tablename}}", null);
$this->assertDebuggingNotCalled();
$this->assertEquals(6, count($records));
- $CFG->debug = DEBUG_DEVELOPER;
+ set_debugging(DEBUG_DEVELOPER);
// negative limits = no limits
$records = $DB->get_records_sql("SELECT * FROM {{$tablename}} ORDER BY id", null, -1, -1);
@@ -1734,8 +1732,6 @@ class core_dml_testcase extends database_driver_testcase {
}
public function test_get_record_sql() {
- global $CFG;
-
$DB = $this->tdb;
$dbman = $DB->get_manager();
@@ -1774,10 +1770,10 @@ class core_dml_testcase extends database_driver_testcase {
$this->assertNotEmpty($DB->get_record_sql("SELECT * FROM {{$tablename}}", array(), IGNORE_MISSING));
$this->assertDebuggingCalled();
- $CFG->debug = DEBUG_MINIMAL;
+ set_debugging(DEBUG_MINIMAL);
$this->assertNotEmpty($DB->get_record_sql("SELECT * FROM {{$tablename}}", array(), IGNORE_MISSING));
$this->assertDebuggingNotCalled();
- $CFG->debug = DEBUG_DEVELOPER;
+ set_debugging(DEBUG_DEVELOPER);
// multiple matches ignored
$this->assertNotEmpty($DB->get_record_sql("SELECT * FROM {{$tablename}}", array(), IGNORE_MULTIPLE));
@@ -3573,7 +3569,7 @@ class core_dml_testcase extends database_driver_testcase {
$this->assertEquals(next($records)->nametext, '91.10');
}
- function sql_compare_text() {
+ public function test_sql_compare_text() {
$DB = $this->tdb;
$dbman = $DB->get_manager();
@@ -3588,15 +3584,43 @@ class core_dml_testcase extends database_driver_testcase {
$DB->insert_record($tablename, array('name'=>'abcd', 'description'=>'abcd'));
$DB->insert_record($tablename, array('name'=>'abcdef', 'description'=>'bbcdef'));
- $DB->insert_record($tablename, array('name'=>'aaaabb', 'description'=>'aaaacccccccccccccccccc'));
+ $DB->insert_record($tablename, array('name'=>'aaaa', 'description'=>'aaaacccccccccccccccccc'));
+ $DB->insert_record($tablename, array('name'=>'xxxx', 'description'=>'123456789a123456789b123456789c123456789d'));
+ // Only some supported databases truncate TEXT fields for comparisons, currently MSSQL and Oracle.
+ $dbtruncatestextfields = ($DB->get_dbfamily() == 'mssql' || $DB->get_dbfamily() == 'oracle');
+
+ if ($dbtruncatestextfields) {
+ // Ensure truncation behaves as expected.
+
+ $sql = "SELECT " . $DB->sql_compare_text('description') . " AS field FROM {{$tablename}} WHERE name = ?";
+ $description = $DB->get_field_sql($sql, array('xxxx'));
+
+ // Should truncate to 32 chars (the default).
+ $this->assertEquals('123456789a123456789b123456789c12', $description);
+
+ $sql = "SELECT " . $DB->sql_compare_text('description', 35) . " AS field FROM {{$tablename}} WHERE name = ?";
+ $description = $DB->get_field_sql($sql, array('xxxx'));
+
+ // Should truncate to the specified number of chars.
+ $this->assertEquals('123456789a123456789b123456789c12345', $description);
+ }
+
+ // Ensure text field comparison is successful.
$sql = "SELECT * FROM {{$tablename}} WHERE name = ".$DB->sql_compare_text('description');
$records = $DB->get_records_sql($sql);
- $this->assertEquals(count($records), 1);
+ $this->assertCount(1, $records);
$sql = "SELECT * FROM {{$tablename}} WHERE name = ".$DB->sql_compare_text('description', 4);
$records = $DB->get_records_sql($sql);
- $this->assertEquals(count($records), 2);
+ if ($dbtruncatestextfields) {
+ // Should truncate description to 4 characters before comparing.
+ $this->assertCount(2, $records);
+ } else {
+ // Should leave untruncated, so one less match.
+ $this->assertCount(1, $records);
+ }
+
}
function test_unique_index_collation_trouble() {
diff --git a/lib/editor/tinymce/classes/plugin.php b/lib/editor/tinymce/classes/plugin.php
index ed33fa49efc..de303006de4 100644
--- a/lib/editor/tinymce/classes/plugin.php
+++ b/lib/editor/tinymce/classes/plugin.php
@@ -360,7 +360,7 @@ abstract class editor_tinymce_plugin {
// Version number comes from plugin version.php, except in developer
// mode where the special string 'dev' is used (prevents cacheing and
// serves unminified JS).
- if (debugging('', DEBUG_DEVELOPER)) {
+ if ($CFG->debugdeveloper) {
$version = '-1';
} else {
$version = $this->get_version();
diff --git a/lib/editor/tinymce/cli/update_lang_files.php b/lib/editor/tinymce/cli/update_lang_files.php
index 790d42e44f9..1d692229a63 100644
--- a/lib/editor/tinymce/cli/update_lang_files.php
+++ b/lib/editor/tinymce/cli/update_lang_files.php
@@ -26,7 +26,7 @@ define('CLI_SCRIPT', true);
require __DIR__ . '/../../../../config.php';
-if (!debugging('', DEBUG_DEVELOPER)) {
+if (!$CFG->debugdeveloper) {
die('Only for developers!!!!!');
}
diff --git a/lib/editor/tinymce/lib.php b/lib/editor/tinymce/lib.php
index eae4a60dd5a..838f845dcdd 100644
--- a/lib/editor/tinymce/lib.php
+++ b/lib/editor/tinymce/lib.php
@@ -100,7 +100,7 @@ class tinymce_texteditor extends texteditor {
public function use_editor($elementid, array $options=null, $fpoptions=null) {
global $PAGE, $CFG;
// Note: use full moodle_url instance to prevent standard JS loader, make sure we are using https on profile page if required.
- if (debugging('', DEBUG_DEVELOPER)) {
+ if ($CFG->debugdeveloper) {
$PAGE->requires->js(new moodle_url($CFG->httpswwwroot.'/lib/editor/tinymce/tiny_mce/'.$this->version.'/tiny_mce_src.js'));
} else {
$PAGE->requires->js(new moodle_url($CFG->httpswwwroot.'/lib/editor/tinymce/tiny_mce/'.$this->version.'/tiny_mce.js'));
diff --git a/lib/enrollib.php b/lib/enrollib.php
index d3a878e091d..a9741f9824f 100644
--- a/lib/enrollib.php
+++ b/lib/enrollib.php
@@ -298,7 +298,7 @@ function enrol_get_shared_courses($user1, $user2, $preloadcontexts = false, $che
} else {
$courses = $DB->get_records_sql($sql, $params);
if ($preloadcontexts) {
- array_map('context_instance_preload', $courses);
+ array_map('context_helper::preload_from_record', $courses);
}
return $courses;
}
diff --git a/lib/filestorage/file_storage.php b/lib/filestorage/file_storage.php
index 9eeca4aab7b..3783eeeba52 100644
--- a/lib/filestorage/file_storage.php
+++ b/lib/filestorage/file_storage.php
@@ -1624,6 +1624,8 @@ class file_storage {
* @return array (contenthash, filesize, newfile)
*/
public function add_file_to_pool($pathname, $contenthash = NULL) {
+ global $CFG;
+
if (!is_readable($pathname)) {
throw new file_exception('storedfilecannotread', '', $pathname);
}
@@ -1635,14 +1637,14 @@ class file_storage {
if (is_null($contenthash)) {
$contenthash = sha1_file($pathname);
- } else if (debugging('', DEBUG_DEVELOPER)) {
+ } else if ($CFG->debugdeveloper) {
$filehash = sha1_file($pathname);
if ($filehash === false) {
throw new file_exception('storedfilecannotread', '', $pathname);
}
if ($filehash !== $contenthash) {
// Hopefully this never happens, if yes we need to fix calling code.
- debugging("Invalid contenthash submitted for file $pathname");
+ debugging("Invalid contenthash submitted for file $pathname", DEBUG_DEVELOPER);
$contenthash = $filehash;
}
}
diff --git a/lib/formslib.php b/lib/formslib.php
index b4c23ac62c3..a0db43a4174 100644
--- a/lib/formslib.php
+++ b/lib/formslib.php
@@ -61,7 +61,7 @@ function pear_handle_error($error){
print_object($error->backtrace);
}
-if (!empty($CFG->debug) and ($CFG->debug >= DEBUG_ALL or $CFG->debug == -1)){
+if ($CFG->debugdeveloper) {
//TODO: this is a wrong place to init PEAR!
$GLOBALS['_PEAR_default_error_mode'] = PEAR_ERROR_CALLBACK;
$GLOBALS['_PEAR_default_error_options'] = 'pear_handle_error';
@@ -1261,7 +1261,9 @@ abstract class moodleform {
* @return void
*/
private function detectMissingSetType() {
- if (!debugging('', DEBUG_DEVELOPER)) {
+ global $CFG;
+
+ if (!$CFG->debugdeveloper) {
// Only for devs.
return;
}
@@ -2143,6 +2145,8 @@ function qf_errorHandler(element, _qfMsg) {
errorSpan.id = \'id_error_\'+element.name;
errorSpan.className = "error";
element.parentNode.insertBefore(errorSpan, element.parentNode.firstChild);
+ document.getElementById(errorSpan.id).setAttribute(\'TabIndex\', \'0\');
+ document.getElementById(errorSpan.id).focus();
}
while (errorSpan.firstChild) {
@@ -2150,11 +2154,14 @@ function qf_errorHandler(element, _qfMsg) {
}
errorSpan.appendChild(document.createTextNode(_qfMsg.substring(3)));
- errorSpan.appendChild(document.createElement("br"));
if (div.className.substr(div.className.length - 6, 6) != " error"
- && div.className != "error") {
- div.className += " error";
+ && div.className != "error") {
+ div.className += " error";
+ linebreak = document.createElement("br");
+ linebreak.className = "error";
+ linebreak.id = \'id_error_break_\'+element.name;
+ errorSpan.parentNode.insertBefore(linebreak, errorSpan.nextSibling);
}
return false;
@@ -2163,6 +2170,10 @@ function qf_errorHandler(element, _qfMsg) {
if (errorSpan) {
errorSpan.parentNode.removeChild(errorSpan);
}
+ var linebreak = document.getElementById(\'id_error_break_\'+element.name);
+ if (linebreak) {
+ linebreak.parentNode.removeChild(linebreak);
+ }
if (div.className.substr(div.className.length - 6, 6) == " error") {
div.className = div.className.substr(0, div.className.length - 6);
@@ -2210,7 +2221,7 @@ function validate_' . $this->_formName . '_' . $escapedElementName . '(element)
ret = validate_' . $this->_formName . '_' . $escapedElementName.'(frm.elements[\''.$elementName.'\']) && ret;
if (!ret && !first_focus) {
first_focus = true;
- frm.elements[\''.$elementName.'\'].focus();
+ document.getElementById(\'id_error_'.$elementName.'\').focus();
}
';
diff --git a/lib/installlib.php b/lib/installlib.php
index 461f552ff84..f60605b1eef 100644
--- a/lib/installlib.php
+++ b/lib/installlib.php
@@ -424,6 +424,7 @@ function install_cli_database(array $options, $interactive) {
@ini_set('display_errors', '1');
$CFG->debug = (E_ALL | E_STRICT);
$CFG->debugdisplay = true;
+ $CFG->debugdeveloper = true;
$CFG->version = '';
$CFG->release = '';
diff --git a/lib/minify/config.php b/lib/minify/config.php
index bf8fc20c9bf..0750f229ae9 100644
--- a/lib/minify/config.php
+++ b/lib/minify/config.php
@@ -14,7 +14,7 @@ defined('MOODLE_INTERNAL') || die(); // start of moodle modification
$min_enableBuilder = false;
$min_errorLogger = false;
-$min_allowDebugFlag = debugging('', DEBUG_DEVELOPER);
+$min_allowDebugFlag = $CFG->debugdeveloper;
$min_cachePath = $CFG->tempdir;
$min_documentRoot = $CFG->dirroot.'/lib/minify';
$min_cacheFileLocking = empty($CFG->preventfilelocking);
diff --git a/lib/modinfolib.php b/lib/modinfolib.php
index 990fc6a45f9..235e3bf4b23 100644
--- a/lib/modinfolib.php
+++ b/lib/modinfolib.php
@@ -242,7 +242,7 @@ class course_modinfo extends stdClass {
* @param int $userid User ID
*/
public function __construct($course, $userid) {
- global $CFG, $DB;
+ global $CFG, $DB, $COURSE, $SITE;
// Check modinfo field is set. If not, build and load it.
if (empty($course->modinfo) || empty($course->sectioncache)) {
@@ -288,8 +288,28 @@ class course_modinfo extends stdClass {
}
// If we haven't already preloaded contexts for the course, do it now
+ // Modules are also cached here as long as it's the first time this course has been preloaded.
context_helper::preload_course($course->id);
+ // Quick integrity check: as a result of race conditions modinfo may not be regenerated after the change.
+ // It is especially dangerous if modinfo contains the deleted course module, as it results in fatal error.
+ // We can check it very cheap by validating the existence of module context.
+ if ($course->id == $COURSE->id || $course->id == $SITE->id) {
+ // Only verify current course (or frontpage) as pages with many courses may not have module contexts cached.
+ // (Uncached modules will result in a very slow verification).
+ foreach ($info as $mod) {
+ if (!context_module::instance($mod->cm, IGNORE_MISSING)) {
+ debugging('Course cache integrity check failed: course module with id '. $mod->cm.
+ ' does not have context. Rebuilding cache for course '. $course->id);
+ rebuild_course_cache($course->id);
+ $this->course = $DB->get_record('course', array('id' => $course->id), '*', MUST_EXIST);
+ $info = unserialize($this->course->modinfo);
+ $sectioncache = unserialize($this->course->sectioncache);
+ break;
+ }
+ }
+ }
+
// Loop through each piece of module data, constructing it
$modexists = array();
foreach ($info as $mod) {
@@ -1079,7 +1099,8 @@ class cm_info extends stdClass {
$this->indent = isset($mod->indent) ? $mod->indent : 0;
$this->extra = isset($mod->extra) ? $mod->extra : '';
$this->extraclasses = isset($mod->extraclasses) ? $mod->extraclasses : '';
- $this->iconurl = isset($mod->iconurl) ? $mod->iconurl : '';
+ // iconurl may be stored as either string or instance of moodle_url.
+ $this->iconurl = isset($mod->iconurl) ? new moodle_url($mod->iconurl) : '';
$this->onclick = isset($mod->onclick) ? $mod->onclick : '';
$this->content = isset($mod->content) ? $mod->content : '';
$this->icon = isset($mod->icon) ? $mod->icon : '';
diff --git a/lib/moodlelib.php b/lib/moodlelib.php
index cfab5d8cbe1..3a42fddbccc 100644
--- a/lib/moodlelib.php
+++ b/lib/moodlelib.php
@@ -4859,10 +4859,15 @@ function delete_course($courseorid, $showfeedback = true) {
$DB->delete_records("course", array("id" => $courseid));
$DB->delete_records("course_format_options", array("courseid" => $courseid));
- // Trigger events.
- $course->context = $context;
- // You can not fetch context in the event because it was already deleted.
- events_trigger('course_deleted', $course);
+ // Trigger a course deleted event.
+ $event = \core\event\course_deleted::create(array(
+ 'objectid' => $course->id,
+ 'context' => $context,
+ 'other' => array('shortname' => $course->shortname,
+ 'fullname' => $course->fullname)
+ ));
+ $event->add_record_snapshot('course', $course);
+ $event->trigger();
return true;
}
@@ -4888,6 +4893,7 @@ function delete_course($courseorid, $showfeedback = true) {
*/
function remove_course_contents($courseid, $showfeedback = true, array $options = null) {
global $CFG, $DB, $OUTPUT;
+
require_once($CFG->libdir.'/badgeslib.php');
require_once($CFG->libdir.'/completionlib.php');
require_once($CFG->libdir.'/questionlib.php');
@@ -5127,10 +5133,16 @@ function remove_course_contents($courseid, $showfeedback = true, array $options
// also some non-standard unsupported plugins may try to store something there.
fulldelete($CFG->dataroot.'/'.$course->id);
- // Finally trigger the event.
- $course->context = $coursecontext; // You can not access context in cron event later after course is deleted.
- $course->options = $options; // Not empty if we used any crazy hack.
- events_trigger('course_content_removed', $course);
+ // Trigger a course content deleted event.
+ $event = \core\event\course_content_deleted::create(array(
+ 'objectid' => $course->id,
+ 'context' => $coursecontext,
+ 'other' => array('shortname' => $course->shortname,
+ 'fullname' => $course->fullname,
+ 'options' => $options) // Passing this for legacy reasons.
+ ));
+ $event->add_record_snapshot('course', $course);
+ $event->trigger();
return true;
}
@@ -6728,8 +6740,8 @@ function get_string($identifier, $component = '', $a = null, $lazyload = false)
return new lang_string($identifier, $component, $a);
}
- if (debugging('', DEBUG_DEVELOPER) && clean_param($identifier, PARAM_STRINGID) === '') {
- throw new coding_exception('Invalid string identifier. The identifier cannot be empty. Please fix your get_string() call.');
+ if ($CFG->debugdeveloper && clean_param($identifier, PARAM_STRINGID) === '') {
+ throw new coding_exception('Invalid string identifier. The identifier cannot be empty. Please fix your get_string() call.', DEBUG_DEVELOPER);
}
// There is now a forth argument again, this time it is a boolean however so
@@ -7222,7 +7234,7 @@ function get_list_of_plugins($directory='mod', $exclude='', $basedir='') {
$basedir = $basedir .'/'. $directory;
}
- if (empty($exclude) and debugging('', DEBUG_DEVELOPER)) {
+ if ($CFG->debugdeveloper and empty($exclude)) {
// Make sure devs do not use this to list normal plugins,
// this is intended for general directories that are not plugins!
@@ -10068,8 +10080,8 @@ class lang_string {
// Check if we need to process the string.
if ($this->string === null) {
// Check the quality of the identifier.
- if (debugging('', DEBUG_DEVELOPER) && clean_param($this->identifier, PARAM_STRINGID) === '') {
- throw new coding_exception('Invalid string identifier. Most probably some illegal character is part of the string identifier. Please check your string definition');
+ if ($CFG->debugdeveloper && clean_param($this->identifier, PARAM_STRINGID) === '') {
+ throw new coding_exception('Invalid string identifier. Most probably some illegal character is part of the string identifier. Please check your string definition', DEBUG_DEVELOPER);
}
// Process the string.
diff --git a/lib/navigationlib.php b/lib/navigationlib.php
index 1fa0f0c9113..3a897d78361 100644
--- a/lib/navigationlib.php
+++ b/lib/navigationlib.php
@@ -3742,7 +3742,7 @@ class settings_navigation extends navigation_node {
require_once($file);
}
- $modulenode = $this->add(get_string('pluginadministration', $this->page->activityname));
+ $modulenode = $this->add(get_string('pluginadministration', $this->page->activityname), null, self::TYPE_SETTING, null, 'modulesettings');
$modulenode->force_open();
// Settings for the module
@@ -4149,7 +4149,7 @@ class settings_navigation extends navigation_node {
protected function load_block_settings() {
global $CFG;
- $blocknode = $this->add($this->context->get_context_name());
+ $blocknode = $this->add($this->context->get_context_name(), null, self::TYPE_SETTING, null, 'blocksettings');
$blocknode->force_open();
// Assign local roles
diff --git a/lib/outputlib.php b/lib/outputlib.php
index 08da9d89ea3..be02e35db1e 100644
--- a/lib/outputlib.php
+++ b/lib/outputlib.php
@@ -1678,7 +1678,8 @@ class xhtml_container_stack {
* Constructor
*/
public function __construct() {
- $this->isdebugging = debugging('', DEBUG_DEVELOPER);
+ global $CFG;
+ $this->isdebugging = $CFG->debugdeveloper;
}
/**
diff --git a/lib/outputrenderers.php b/lib/outputrenderers.php
index be2dfea5836..d61279a473b 100644
--- a/lib/outputrenderers.php
+++ b/lib/outputrenderers.php
@@ -2532,7 +2532,7 @@ EOD;
}
$output .= $this->box($message, 'errorbox', null, array('data-rel' => 'fatalerror'));
- if (debugging('', DEBUG_DEVELOPER)) {
+ if ($CFG->debugdeveloper) {
if (!empty($debuginfo)) {
$debuginfo = s($debuginfo); // removes all nasty JS
$debuginfo = str_replace("\n", ' ', $debuginfo); // keep newlines
@@ -3364,7 +3364,7 @@ class core_renderer_cli extends core_renderer {
public function fatal_error($message, $moreinfourl, $link, $backtrace, $debuginfo = null) {
$output = "!!! $message !!!\n";
- if (debugging('', DEBUG_DEVELOPER)) {
+ if ($CFG->debugdeveloper) {
if (!empty($debuginfo)) {
$output .= $this->notification($debuginfo, 'notifytiny');
}
diff --git a/lib/outputrequirementslib.php b/lib/outputrequirementslib.php
index 4374c77a058..baabc5ca7d8 100644
--- a/lib/outputrequirementslib.php
+++ b/lib/outputrequirementslib.php
@@ -224,7 +224,7 @@ class page_requirements_manager {
));
// Set some more loader options applying to groups too.
- if (debugging('', DEBUG_DEVELOPER)) {
+ if ($CFG->debugdeveloper) {
// When debugging is enabled, we want to load the non-minified (RAW) versions of YUI library modules rather
// than the DEBUG versions as these generally generate too much logging for our purposes.
// However we do want the DEBUG versions of our Moodle-specific modules.
@@ -270,7 +270,7 @@ class page_requirements_manager {
'jsrev' => ((empty($CFG->cachejs) or empty($CFG->jsrev)) ? -1 : $CFG->jsrev),
'svgicons' => $page->theme->use_svg_icons()
);
- if (debugging('', DEBUG_DEVELOPER)) {
+ if ($CFG->debugdeveloper) {
$this->M_cfg['developerdebug'] = true;
}
@@ -300,6 +300,11 @@ class page_requirements_manager {
if (!empty($page->cm->id)) {
$params['cmid'] = $page->cm->id;
}
+ // Strings for drag and drop.
+ $this->strings_for_js(array('movecontent',
+ 'tocontent',
+ 'emptydragdropregion'),
+ 'moodle');
$page->requires->yui_module('moodle-core-blocks', 'M.core_blocks.init_dragdrop', array($params), null, true);
}
}
@@ -436,7 +441,7 @@ class page_requirements_manager {
$this->jqueryplugins[$plugin]->urls = array();
foreach ($plugins[$plugin]['files'] as $file) {
- if (debugging('', DEBUG_DEVELOPER)) {
+ if ($CFG->debugdeveloper) {
if (!file_exists("$componentdir/jquery/$file")) {
debugging("Invalid file '$file' specified in jQuery plugin '$plugin' in component '$component'");
continue;
@@ -746,7 +751,7 @@ class page_requirements_manager {
// Don't load this module if we already have, no need to!
if ($this->js_module_loaded($module['name'])) {
- if (debugging('', DEBUG_DEVELOPER)) {
+ if ($CFG->debugdeveloper) {
$this->debug_moduleloadstacktraces[$module['name']][] = format_backtrace(debug_backtrace());
}
return;
@@ -780,7 +785,7 @@ class page_requirements_manager {
} else {
$this->YUI_config->add_module_config($module['name'], $module);
}
- if (debugging('', DEBUG_DEVELOPER)) {
+ if ($CFG->debugdeveloper) {
if (!array_key_exists($module['name'], $this->debug_moduleloadstacktraces)) {
$this->debug_moduleloadstacktraces[$module['name']] = array();
}
diff --git a/lib/phpmailer/moodle_phpmailer.php b/lib/phpmailer/moodle_phpmailer.php
index cf2563a759c..01cdadc018d 100644
--- a/lib/phpmailer/moodle_phpmailer.php
+++ b/lib/phpmailer/moodle_phpmailer.php
@@ -125,4 +125,26 @@ class moodle_phpmailer extends PHPMailer {
fclose($fp);
return $out;
}
+
+ /**
+ * Sends this mail.
+ *
+ * This function has been overridden to facilitate unit testing.
+ *
+ * @return bool
+ */
+ protected function PostSend() {
+ // Now ask phpunit if it wants to catch this message.
+ if (PHPUNIT_TEST && phpunit_util::is_redirecting_messages()) {
+ $mail = new stdClass();
+ $mail->header = $this->MIMEHeader;
+ $mail->body = $this->MIMEBody;
+ $mail->subject = $this->Subject;
+ $mail->from = $this->From;
+ phpunit_util::phpmailer_sent($mail);
+ return true;
+ } else {
+ return parent::PostSend();
+ }
+ }
}
diff --git a/lib/phpunit/bootstrap.php b/lib/phpunit/bootstrap.php
index 6dee6211041..749bcfa8b73 100644
--- a/lib/phpunit/bootstrap.php
+++ b/lib/phpunit/bootstrap.php
@@ -198,6 +198,7 @@ unset($productioncfg);
// force the same CFG settings in all sites
$CFG->debug = (E_ALL | E_STRICT); // can not use DEBUG_DEVELOPER yet
+$CFG->debugdeveloper = true;
$CFG->debugdisplay = 1;
error_reporting($CFG->debug);
ini_set('display_errors', '1');
diff --git a/lib/phpunit/classes/advanced_testcase.php b/lib/phpunit/classes/advanced_testcase.php
index 0a4848d9141..4b60ec3a7c4 100644
--- a/lib/phpunit/classes/advanced_testcase.php
+++ b/lib/phpunit/classes/advanced_testcase.php
@@ -248,7 +248,8 @@ abstract class advanced_testcase extends PHPUnit_Framework_TestCase {
}
/**
- * Clear all previous debugging messages in current test.
+ * Clear all previous debugging messages in current test
+ * and revert to default DEVELOPER_DEBUG level.
*/
public function resetDebugging() {
phpunit_util::reset_debugging();
@@ -352,6 +353,19 @@ abstract class advanced_testcase extends PHPUnit_Framework_TestCase {
return phpunit_util::start_message_redirection();
}
+ /**
+ * Starts email redirection.
+ *
+ * You can verify if email were sent or not by inspecting the email
+ * array in the returned phpmailer sink instance. The redirection
+ * can be stopped by calling $sink->close();
+ *
+ * @return phpunit_message_sink
+ */
+ public function redirectEmails() {
+ return phpunit_util::start_phpmailer_redirection();
+ }
+
/**
* Starts event redirection.
*
diff --git a/lib/phpunit/classes/phpmailer_sink.php b/lib/phpunit/classes/phpmailer_sink.php
new file mode 100644
index 00000000000..bb00478144e
--- /dev/null
+++ b/lib/phpunit/classes/phpmailer_sink.php
@@ -0,0 +1,87 @@
+.
+
+/**
+ * phpmailer message sink.
+ *
+ * @package core
+ * @category phpunit
+ * @copyright 2013 Andrew Nicols
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+
+/**
+ * phpmailer message sink.
+ *
+ * @package core
+ * @category phpunit
+ * @copyright 2013 Andrew Nicols
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class phpunit_phpmailer_sink {
+ /**
+ * @var array of records which would have been sent by phpmailer.
+ */
+ protected $messages = array();
+
+ /**
+ * Stop message redirection.
+ *
+ * Use if you do not want message redirected any more.
+ */
+ public function close() {
+ phpunit_util::stop_phpmailer_redirection();
+ }
+
+ /**
+ * To be called from phpunit_util only!
+ *
+ * @param stdClass $message record from message_read table
+ */
+ public function add_message($message) {
+ /* Number messages from 0. */
+ $this->messages[] = $message;
+ }
+
+ /**
+ * Returns all redirected messages.
+ *
+ * The instances are records form the message_read table.
+ * The array indexes are numbered from 0 and the order is matching
+ * the creation of events.
+ *
+ * @return array
+ */
+ public function get_messages() {
+ return $this->messages;
+ }
+
+ /**
+ * Return number of messages redirected to this sink.
+ * @return int
+ */
+ public function count() {
+ return count($this->messages);
+ }
+
+ /**
+ * Removes all previously stored messages.
+ */
+ public function clear() {
+ $this->messages = array();
+ }
+}
diff --git a/lib/phpunit/classes/util.php b/lib/phpunit/classes/util.php
index 552e78aaca8..a48f71daeef 100644
--- a/lib/phpunit/classes/util.php
+++ b/lib/phpunit/classes/util.php
@@ -43,6 +43,9 @@ class phpunit_util extends testing_util {
/** @var phpunit_message_sink alternative target for moodle messaging */
protected static $messagesink = null;
+ /** @var phpunit_phpmailer_sink alternative target for phpmailer messaging */
+ protected static $phpmailersink = null;
+
/** @var phpunit_message_sink alternative target for moodle messaging */
protected static $eventsink = null;
@@ -98,6 +101,9 @@ class phpunit_util extends testing_util {
// Stop any message redirection.
phpunit_util::stop_message_redirection();
+ // Stop any message redirection.
+ phpunit_util::stop_phpmailer_redirection();
+
// Stop any message redirection.
phpunit_util::stop_event_redirection();
@@ -594,6 +600,7 @@ class phpunit_util extends testing_util {
*/
public static function reset_debugging() {
self::$debuggings = array();
+ set_debugging(DEBUG_DEVELOPER);
}
/**
@@ -668,6 +675,55 @@ class phpunit_util extends testing_util {
}
}
+ /**
+ * Start phpmailer redirection.
+ *
+ * Note: Do not call directly from tests,
+ * use $sink = $this->redirectEmails() instead.
+ *
+ * @return phpunit_phpmailer_sink
+ */
+ public static function start_phpmailer_redirection() {
+ if (self::$phpmailersink) {
+ self::stop_phpmailer_redirection();
+ }
+ self::$phpmailersink = new phpunit_phpmailer_sink();
+ return self::$phpmailersink;
+ }
+
+ /**
+ * End phpmailer redirection.
+ *
+ * Note: Do not call directly from tests,
+ * use $sink->close() instead.
+ */
+ public static function stop_phpmailer_redirection() {
+ self::$phpmailersink = null;
+ }
+
+ /**
+ * Are messages for phpmailer redirected to some sink?
+ *
+ * Note: to be called from moodle_phpmailer.php only!
+ *
+ * @return bool
+ */
+ public static function is_redirecting_phpmailer() {
+ return !empty(self::$phpmailersink);
+ }
+
+ /**
+ * To be called from messagelib.php only!
+ *
+ * @param stdClass $message record from message_read table
+ * @return bool true means send message, false means message "sent" to sink.
+ */
+ public static function phpmailer_sent($message) {
+ if (self::$phpmailersink) {
+ self::$phpmailersink->add_message($message);
+ }
+ }
+
/**
* Start event redirection.
*
diff --git a/lib/phpunit/lib.php b/lib/phpunit/lib.php
index 9f087609258..86f8e413434 100644
--- a/lib/phpunit/lib.php
+++ b/lib/phpunit/lib.php
@@ -32,6 +32,7 @@ require_once(__DIR__.'/classes/util.php');
require_once(__DIR__.'/classes/event_mock.php');
require_once(__DIR__.'/classes/event_sink.php');
require_once(__DIR__.'/classes/message_sink.php');
+require_once(__DIR__.'/classes/phpmailer_sink.php');
require_once(__DIR__.'/classes/basic_testcase.php');
require_once(__DIR__.'/classes/database_driver_testcase.php');
require_once(__DIR__.'/classes/arraydataset.php');
diff --git a/lib/phpunit/tests/advanced_test.php b/lib/phpunit/tests/advanced_test.php
index 61f5a30f951..f6c071908c9 100644
--- a/lib/phpunit/tests/advanced_test.php
+++ b/lib/phpunit/tests/advanced_test.php
@@ -65,9 +65,10 @@ class core_phpunit_advanced_testcase extends advanced_testcase {
$debuggings = $this->getDebuggingMessages();
$this->assertEquals(0, count($debuggings));
- $CFG->debug = DEBUG_NONE;
+ set_debugging(DEBUG_NONE);
debugging('hokus');
$this->assertDebuggingNotCalled();
+ set_debugging(DEBUG_DEVELOPER);
}
public function test_set_user() {
diff --git a/lib/pluginlib.php b/lib/pluginlib.php
index cf7e1464d04..6e83947f1a7 100644
--- a/lib/pluginlib.php
+++ b/lib/pluginlib.php
@@ -3341,7 +3341,7 @@ class plugininfo_mod extends plugininfo_base {
*/
protected function load_version_php($disablecache=false) {
- $cache = cache::make('core', 'plugininfo_base');
+ $cache = cache::make('core', 'plugininfo_mod');
$versionsphp = $cache->get('versions_php');
diff --git a/lib/sessionlib.php b/lib/sessionlib.php
index be19e443eee..8063106df65 100644
--- a/lib/sessionlib.php
+++ b/lib/sessionlib.php
@@ -1163,11 +1163,13 @@ function session_get_realuser() {
* @return void
*/
function session_loginas($userid, $context) {
+ global $USER;
+
if (session_is_loggedinas()) {
return;
}
- // switch to fresh new $SESSION
+ // Switch to fresh new $SESSION.
$_SESSION['REALSESSION'] = $_SESSION['SESSION'];
$_SESSION['SESSION'] = new stdClass();
@@ -1177,10 +1179,24 @@ function session_loginas($userid, $context) {
$user->realuser = $_SESSION['REALUSER']->id;
$user->loginascontext = $context;
- // let enrol plugins deal with new enrolments if necessary
+ // Let enrol plugins deal with new enrolments if necessary.
enrol_check_plugins($user);
- // set up global $USER
+
+ // Create event before $USER is updated.
+ $event = \core\event\user_loggedinas::create(
+ array(
+ 'objectid' => $USER->id,
+ 'context' => $context,
+ 'relateduserid' => $userid,
+ 'other' => array(
+ 'originalusername' => fullname($USER, true),
+ 'loggedinasusername' => fullname($user, true)
+ )
+ )
+ );
+ // Set up global $USER.
session_set_user($user);
+ $event->trigger();
}
/**
diff --git a/lib/setup.php b/lib/setup.php
index acc466adfc9..ecf49176559 100644
--- a/lib/setup.php
+++ b/lib/setup.php
@@ -143,6 +143,11 @@ if (!defined('BEHAT_SITE_RUNNING') && !empty($CFG->behat_dataroot) &&
}
}
+// Make sure there is some database table prefix.
+if (!isset($CFG->prefix)) {
+ $CFG->prefix = '';
+}
+
// Define admin directory
if (!isset($CFG->admin)) { // Just in case it isn't defined in config.php
$CFG->admin = 'admin'; // This is relative to the wwwroot and dirroot
@@ -166,6 +171,16 @@ if (!isset($CFG->localcachedir)) {
$CFG->localcachedir = "$CFG->dataroot/localcache";
}
+// Location of all languages except core English pack.
+if (!isset($CFG->langotherroot)) {
+ $CFG->langotherroot = $CFG->dataroot.'/lang';
+}
+
+// Location of local lang pack customisations (dirs with _local suffix).
+if (!isset($CFG->langlocalroot)) {
+ $CFG->langlocalroot = $CFG->dataroot.'/lang';
+}
+
// The current directory in PHP version 4.3.0 and above isn't necessarily the
// directory of the script when run from the command line. The require_once()
// would fail, so we'll have to chdir()
@@ -309,6 +324,23 @@ umask($CFG->umaskpermissions);
$CFG->yui2version = '2.9.0';
$CFG->yui3version = '3.9.1';
+// Store settings from config.php in array in $CFG - we can use it later to detect problems and overrides.
+if (!isset($CFG->config_php_settings)) {
+ $CFG->config_php_settings = (array)$CFG;
+ // Forced plugin settings override values from config_plugins table.
+ unset($CFG->config_php_settings['forced_plugin_settings']);
+ if (!isset($CFG->forced_plugin_settings)) {
+ $CFG->forced_plugin_settings = array();
+ }
+}
+
+if (isset($CFG->debug)) {
+ $CFG->debug = (int)$CFG->debug;
+} else {
+ $CFG->debug = 0;
+}
+$CFG->debugdeveloper = (($CFG->debug & (E_ALL | E_STRICT)) === (E_ALL | E_STRICT)); // DEBUG_DEVELOPER is not available yet.
+
if (!defined('MOODLE_INTERNAL')) { // Necessary because cli installer has to define it earlier.
/** Used by library scripts to check they are being called by Moodle. */
define('MOODLE_INTERNAL', true);
@@ -321,11 +353,7 @@ require_once($CFG->libdir .'/classes/component.php');
if (defined('ABORT_AFTER_CONFIG')) {
if (!defined('ABORT_AFTER_CONFIG_CANCEL')) {
// hide debugging if not enabled in config.php - we do not want to disclose sensitive info
- if (isset($CFG->debug)) {
- error_reporting($CFG->debug);
- } else {
- error_reporting(0);
- }
+ error_reporting($CFG->debug);
if (NO_DEBUG_DISPLAY) {
// Some parts of Moodle cannot display errors and debug at all.
ini_set('display_errors', '0');
@@ -454,13 +482,6 @@ global $FULLSCRIPT;
*/
global $SCRIPT;
-// Store settings from config.php in array in $CFG - we can use it later to detect problems and overrides
-$CFG->config_php_settings = (array)$CFG;
-// Forced plugin settings override values from config_plugins table
-unset($CFG->config_php_settings['forced_plugin_settings']);
-if (!isset($CFG->forced_plugin_settings)) {
- $CFG->forced_plugin_settings = array();
-}
// Set httpswwwroot default value (this variable will replace $CFG->wwwroot
// inside some URLs used in HTTPSPAGEREQUIRED pages.
$CFG->httpswwwroot = $CFG->wwwroot;
@@ -505,20 +526,6 @@ if (!empty($_SERVER['HTTP_X_moz']) && $_SERVER['HTTP_X_moz'] === 'prefetch'){
exit(1);
}
-if (!isset($CFG->prefix)) { // Just in case it isn't defined in config.php
- $CFG->prefix = '';
-}
-
-// location of all languages except core English pack
-if (!isset($CFG->langotherroot)) {
- $CFG->langotherroot = $CFG->dataroot.'/lang';
-}
-
-// location of local lang pack customisations (dirs with _local suffix)
-if (!isset($CFG->langlocalroot)) {
- $CFG->langlocalroot = $CFG->dataroot.'/lang';
-}
-
//point pear include path to moodles lib/pear so that includes and requires will search there for files before anywhere else
//the problem is that we need specific version of quickforms and hacked excel files :-(
ini_set('include_path', $CFG->libdir.'/pear' . PATH_SEPARATOR . ini_get('include_path'));
@@ -579,22 +586,42 @@ if (PHPUNIT_TEST and !PHPUNIT_UTIL) {
unset($dbhash);
}
-// Disable errors for now - needed for installation when debug enabled in config.php
-if (isset($CFG->debug)) {
- $originalconfigdebug = $CFG->debug;
- unset($CFG->debug);
-} else {
- $originalconfigdebug = null;
-}
-
-// Load up any configuration from the config table
-
+// Load up any configuration from the config table or MUC cache.
if (PHPUNIT_TEST) {
phpunit_util::initialise_cfg();
} else {
initialise_cfg();
}
+if (isset($CFG->debug)) {
+ $CFG->debug = (int)$CFG->debug;
+ error_reporting($CFG->debug);
+} else {
+ $CFG->debug = 0;
+}
+$CFG->debugdeveloper = (($CFG->debug & DEBUG_DEVELOPER) === DEBUG_DEVELOPER);
+
+// Find out if PHP configured to display warnings,
+// this is a security problem because some moodle scripts may
+// disclose sensitive information.
+if (ini_get_bool('display_errors')) {
+ define('WARN_DISPLAY_ERRORS_ENABLED', true);
+}
+// If we want to display Moodle errors, then try and set PHP errors to match.
+if (!isset($CFG->debugdisplay)) {
+ // Keep it "as is" during installation.
+} else if (NO_DEBUG_DISPLAY) {
+ // Some parts of Moodle cannot display errors and debug at all.
+ ini_set('display_errors', '0');
+ ini_set('log_errors', '1');
+} else if (empty($CFG->debugdisplay)) {
+ ini_set('display_errors', '0');
+ ini_set('log_errors', '1');
+} else {
+ // This is very problematic in XHTML strict mode!
+ ini_set('display_errors', '1');
+}
+
// Verify upgrade is not running unless we are in a script that needs to execute in any case
if (!defined('NO_UPGRADE_CHECK') and isset($CFG->upgraderunning)) {
if ($CFG->upgraderunning < time()) {
@@ -609,14 +636,6 @@ if (!empty($CFG->logsql)) {
$DB->set_logging(true);
}
-// Prevent warnings from roles when upgrading with debug on
-if (isset($CFG->debug)) {
- $originaldatabasedebug = $CFG->debug;
- unset($CFG->debug);
-} else {
- $originaldatabasedebug = null;
-}
-
// enable circular reference collector in PHP 5.3,
// it helps a lot when using large complex OOP structures such as in amos or gradebook
if (function_exists('gc_enable')) {
@@ -628,40 +647,6 @@ if (function_exists('register_shutdown_function')) {
register_shutdown_function('moodle_request_shutdown');
}
-// Set error reporting back to normal
-if ($originaldatabasedebug === null) {
- $CFG->debug = DEBUG_MINIMAL;
-} else {
- $CFG->debug = $originaldatabasedebug;
-}
-if ($originalconfigdebug !== null) {
- $CFG->debug = $originalconfigdebug;
-}
-unset($originalconfigdebug);
-unset($originaldatabasedebug);
-error_reporting($CFG->debug);
-
-// find out if PHP configured to display warnings,
-// this is a security problem because some moodle scripts may
-// disclose sensitive information
-if (ini_get_bool('display_errors')) {
- define('WARN_DISPLAY_ERRORS_ENABLED', true);
-}
-// If we want to display Moodle errors, then try and set PHP errors to match
-if (!isset($CFG->debugdisplay)) {
- // keep it "as is" during installation
-} else if (NO_DEBUG_DISPLAY) {
- // some parts of Moodle cannot display errors and debug at all.
- ini_set('display_errors', '0');
- ini_set('log_errors', '1');
-} else if (empty($CFG->debugdisplay)) {
- ini_set('display_errors', '0');
- ini_set('log_errors', '1');
-} else {
- // This is very problematic in XHTML strict mode!
- ini_set('display_errors', '1');
-}
-
// detect unsupported upgrade jump as soon as possible - do not change anything, do not use system functions
if (!empty($CFG->version) and $CFG->version < 2007101509) {
print_error('upgraderequires19', 'error');
diff --git a/lib/setuplib.php b/lib/setuplib.php
index f75a8a9bd04..1b43764f449 100644
--- a/lib/setuplib.php
+++ b/lib/setuplib.php
@@ -729,25 +729,28 @@ function setup_validate_php_configuration() {
}
/**
- * Initialise global $CFG variable
- * @return void
+ * Initialise global $CFG variable.
+ * @private to be used only from lib/setup.php
*/
function initialise_cfg() {
global $CFG, $DB;
+ if (!$DB) {
+ // This should not happen.
+ return;
+ }
+
try {
- if ($DB) {
- $localcfg = get_config('core');
- foreach ($localcfg as $name => $value) {
- if (property_exists($CFG, $name)) {
- // config.php settings always take precedence
- continue;
- }
- $CFG->{$name} = $value;
- }
- }
+ $localcfg = get_config('core');
} catch (dml_exception $e) {
- // most probably empty db, going to install soon
+ // Most probably empty db, going to install soon.
+ return;
+ }
+
+ foreach ($localcfg as $name => $value) {
+ // Note that get_config() keeps forced settings
+ // and normalises values to string if possible.
+ $CFG->{$name} = $value;
}
}
diff --git a/lib/simplepie/moodle_simplepie.php b/lib/simplepie/moodle_simplepie.php
index 98fcca04b1a..769cacbb8c1 100644
--- a/lib/simplepie/moodle_simplepie.php
+++ b/lib/simplepie/moodle_simplepie.php
@@ -154,7 +154,7 @@ class moodle_simplepie_file extends SimplePie_File {
if ($parser->parse()) {
$this->headers = $parser->headers;
- $this->body = $parser->body;
+ $this->body = trim($parser->body);
$this->status_code = $parser->status_code;
diff --git a/lib/tests/completionlib_test.php b/lib/tests/completionlib_test.php
index 112b712f6fa..9286a5f49c4 100644
--- a/lib/tests/completionlib_test.php
+++ b/lib/tests/completionlib_test.php
@@ -809,8 +809,34 @@ class core_completionlib_testcase extends advanced_testcase {
$data = $ccompletion->get_record_data();
$this->assertEventLegacyData($data, $event);
}
-}
+ /**
+ * Test course completed event.
+ */
+ public function test_course_completion_updated_event() {
+ $this->setup_data();
+ $coursecontext = context_course::instance($this->course->id);
+ $coursecompletionevent = \core\event\course_completion_updated::create(
+ array(
+ 'courseid' => $this->course->id,
+ 'context' => $coursecontext
+ )
+ );
+
+ // Mark course as complete and get triggered event.
+ $sink = $this->redirectEvents();
+ $coursecompletionevent->trigger();
+ $events = $sink->get_events();
+ $event = array_pop($events);
+ $sink->close();
+
+ $this->assertInstanceOf('\core\event\course_completion_updated', $event);
+ $this->assertEquals($this->course->id, $event->courseid);
+ $this->assertEquals($coursecontext, $event->get_context());
+ $expectedlegacylog = array($this->course->id, 'course', 'completion updated', 'completion.php?id='.$this->course->id);
+ $this->assertEventLegacyLogData($expectedlegacylog, $event);
+ }
+}
class core_completionlib_fake_recordset implements Iterator {
protected $closed;
diff --git a/lib/tests/csslib_test.php b/lib/tests/csslib_test.php
index b56a390f8ca..a798e20c827 100644
--- a/lib/tests/csslib_test.php
+++ b/lib/tests/csslib_test.php
@@ -39,6 +39,11 @@ require_once($CFG->libdir . '/csslib.php');
*/
class core_csslib_testcase extends advanced_testcase {
+ /**
+ * Returns a CSS optimiser
+ *
+ * @return css_optimiser
+ */
protected function get_optimiser() {
return new css_optimiser();
}
@@ -377,6 +382,9 @@ class core_csslib_testcase extends advanced_testcase {
$this->assertSame($css, $optimiser->process($css));
}
+ /**
+ * Test widths.
+ */
public function test_widths() {
$optimiser = new css_optimiser();
@@ -503,6 +511,9 @@ class core_csslib_testcase extends advanced_testcase {
$this->assertSame($cssout, $optimiser->process($cssin));
}
+ /**
+ * Test cursor optimisations
+ */
public function test_cursor() {
$optimiser = new css_optimiser();
@@ -527,6 +538,9 @@ class core_csslib_testcase extends advanced_testcase {
$this->assertSame($cssout, $optimiser->process($cssin));
}
+ /**
+ * Test vertical align optimisations
+ */
public function test_vertical_align() {
$optimiser = new css_optimiser();
@@ -550,6 +564,9 @@ class core_csslib_testcase extends advanced_testcase {
$this->assertSame($cssout, $optimiser->process($cssin));
}
+ /**
+ * Test float optimisations
+ */
public function test_float() {
$optimiser = new css_optimiser();
@@ -697,7 +714,7 @@ class core_csslib_testcase extends advanced_testcase {
// Test some complex IE css... I couldn't even think of a more complext solution
// than the CSS they came up with.
- $cssin = 'a { opacity: 0.5; -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=50)"; filter: alpha(opacity=50); }';
+ $cssin = 'a { opacity: 0.5;-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=50)"; filter: alpha(opacity=50); }';
$cssout = 'a{opacity:0.5;-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=50)";filter:alpha(opacity=50);}';
$this->assertSame($cssout, $optimiser->process($cssin));
}
@@ -1027,9 +1044,27 @@ CSS;
$cssin = "@media screen and (min-width:30px) {\n #region-main-box{background-color:#000;}\n}\n@media screen and (min-width:31px) {\n #region-main-box{background-color:#FFF;}\n}";
$cssout = "@media screen and (min-width:30px) { #region-main-box{background-color:#000;} }\n@media screen and (min-width:31px) { #region-main-box{background-color:#FFF;} }";
$this->assertSame($cssout, $optimiser->process($cssin));
+
+ $cssin = "@media (min-width: 768px) and (max-width: 979px) {\n*{*zoom:1;}}";
+ $cssout = "@media (min-width: 768px) and (max-width: 979px) { *{*zoom:1;} }";
+ $this->assertSame($cssout, $optimiser->process($cssin));
+
+ $cssin = "#test {min-width:1200px;}@media (min-width: 768px) {#test {min-width: 1024px;}}";
+ $cssout = "#test{min-width:1200px;} \n@media (min-width: 768px) { #test{min-width:1024px;} }";
+ $this->assertSame($cssout, $optimiser->process($cssin));
+
+ $cssin = "@media(min-width:768px){#page-calender-view .container fluid{min-width:1024px}}.section_add_menus{text-align:right}";
+ $cssout = ".section_add_menus{text-align:right;} \n@media (min-width:768px) { #page-calender-view .container fluid{min-width:1024px;} }";
+ $this->assertSame($cssout, $optimiser->process($cssin));
+
+ $cssin = "@-ms-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}";
+ $cssout = "@-ms-keyframes progress-bar-stripes {from{background-position:40px 0;}to{background-position:0 0;}}";
+ $this->assertSame($cssout, $optimiser->process($cssin));
}
-
+ /**
+ * Test the ordering of CSS optimisationss
+ */
public function test_css_optimisation_ordering() {
$optimiser = $this->get_optimiser();
@@ -1041,6 +1076,9 @@ CSS;
$this->assertSame($cssout, $optimiser->process($cssin));
}
+ /**
+ * Test CSS chunking
+ */
public function test_css_chunking() {
// Test with an even number of styles.
$css = 'a{}b{}c{}d{}e{}f{}';
@@ -1143,4 +1181,72 @@ CSS;
$this->assertInternalType('array', $chunks);
// I don't care what the outcome is, I just want to make sure it doesn't die.
}
-}
+
+ /**
+ * Test CSS3.
+ */
+ public function test_css3() {
+ $optimiser = $this->get_optimiser();
+
+ $css = '.test > .test{display:inline-block;}';
+ $this->assertSame($css, $optimiser->process($css));
+
+ $css = '*{display:inline-block;}';
+ $this->assertSame($css, $optimiser->process($css));
+
+ $css = 'div > *{display:inline-block;}';
+ $this->assertSame($css, $optimiser->process($css));
+
+ $css = 'div:nth-child(3){display:inline-block;}';
+ $this->assertSame($css, $optimiser->process($css));
+
+ $css = '.test:nth-child(3){display:inline-block;}';
+ $this->assertSame($css, $optimiser->process($css));
+
+ $css = '*:nth-child(3){display:inline-block;}';
+ $this->assertSame($css, $optimiser->process($css));
+
+ $css = '*[id]{display:inline-block;}';
+ $this->assertSame($css, $optimiser->process($css));
+
+ $css = '*[id=blah]{display:inline-block;}';
+ $this->assertSame($css, $optimiser->process($css));
+
+ $css = '*[*id=blah]{display:inline-block;}';
+ $this->assertSame($css, $optimiser->process($css));
+
+ $css = '*[*id=blah_]{display:inline-block;}';
+ $this->assertSame($css, $optimiser->process($css));
+
+ $css = '*[id^=blah*d]{display:inline-block;}';
+ $this->assertSame($css, $optimiser->process($css));
+
+ $css = '.test{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;}';
+ $this->assertSame($css, $optimiser->process($css));
+
+ $css = '#test{box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);}';
+ $this->assertSame($css, $optimiser->process($css));
+ }
+
+ /**
+ * Test browser hacks here.
+ */
+ public function test_browser_hacks() {
+ $optimiser = $this->get_optimiser();
+
+ $css = '#test{*zoom:1;}';
+ $this->assertSame($css, $optimiser->process($css));
+
+ $css = '.test{width:75%;*width:76%;}';
+ $this->assertSame($css, $optimiser->process($css));
+
+ $css = '#test{*zoom:1;*display:inline;}';
+ $this->assertSame($css, $optimiser->process($css));
+
+ $css = '.test{width:75%;*width:76%;width:76%}';
+ $this->assertSame('.test{width:76%;*width:76%;}', $optimiser->process($css));
+
+ $css = '.test{width:75%;*width:76%;*width:75%}';
+ $this->assertSame('.test{width:75%;*width:75%;}', $optimiser->process($css));
+ }
+}
\ No newline at end of file
diff --git a/lib/tests/environment_test.php b/lib/tests/environment_test.php
index 748a3e0795d..64c508cae5c 100644
--- a/lib/tests/environment_test.php
+++ b/lib/tests/environment_test.php
@@ -33,8 +33,6 @@ class core_environment_testcase extends advanced_testcase {
/**
* Test the environment.
- *
- * @todo MDL-40952 will introduce a way to output something to the user to inform them this has failed.
*/
public function test_environment() {
global $CFG;
@@ -44,11 +42,12 @@ class core_environment_testcase extends advanced_testcase {
$this->assertNotEmpty($envstatus);
foreach ($environment_results as $environment_result) {
- if ($environment_result->getLevel() === 'optional' && $environment_result->getStatus() === false) {
- // An optional environment test has failed, we don't want to fail unit tests because of this.
- // This was first detected with the opcache notice, see the to do in the phpdoc.
- // We are going to fake the assertion count here so that people get consistent numbers.
- $this->addToAssertionCount(1);
+ if ($environment_result->part === 'php_setting'
+ and $environment_result->info === 'opcache.enable'
+ and $environment_result->getLevel() === 'optional'
+ and $environment_result->getStatus() === false
+ ) {
+ $this->markTestSkipped('OPCache extension is not necessary for unit testing.');
continue;
}
$this->assertTrue($environment_result->getStatus(), "Problem detected in environment ($environment_result->part:$environment_result->info), fix all warnings and errors!");
diff --git a/lib/tests/event_test.php b/lib/tests/event_test.php
index 414d105ff9b..5b0d931a31e 100644
--- a/lib/tests/event_test.php
+++ b/lib/tests/event_test.php
@@ -52,7 +52,7 @@ class core_event_testcase extends advanced_testcase {
$this->assertSame('unittest', $event->target);
$this->assertSame(5, $event->objectid);
$this->assertSame('u', $event->crud);
- $this->assertSame(10, $event->level);
+ $this->assertSame(\core\event\base::LEVEL_PARTICIPATING, $event->level);
$this->assertEquals($system, $event->get_context());
$this->assertSame($system->id, $event->contextid);
@@ -602,6 +602,10 @@ class core_event_testcase extends advanced_testcase {
$this->assertInstanceOf('\coding_exception', $e);
}
+ $event = \core_tests\event\bad_event2b::create(array('context'=>\context_system::instance()));
+ @$event->trigger();
+ $this->assertDebuggingCalled();
+
$event = \core_tests\event\bad_event3::create(array('context'=>\context_system::instance()));
@$event->trigger();
$this->assertDebuggingCalled();
@@ -637,10 +641,10 @@ class core_event_testcase extends advanced_testcase {
$event2 = \core_tests\event\problematic_event1::create(array('xxx'=>0, 'context'=>\context_system::instance()));
$this->assertDebuggingCalled();
- $CFG->debug = 0;
+ set_debugging(DEBUG_NONE);
$event3 = \core_tests\event\problematic_event1::create(array('xxx'=>0, 'context'=>\context_system::instance()));
$this->assertDebuggingNotCalled();
- $CFG->debug = E_ALL | E_STRICT;
+ set_debugging(DEBUG_DEVELOPER);
$event4 = \core_tests\event\problematic_event1::create(array('context'=>\context_system::instance(), 'other'=>array('a'=>1)));
$event4->trigger();
diff --git a/lib/tests/fixtures/event_fixtures.php b/lib/tests/fixtures/event_fixtures.php
index a37e2f38bdd..1d775d967ed 100644
--- a/lib/tests/fixtures/event_fixtures.php
+++ b/lib/tests/fixtures/event_fixtures.php
@@ -41,7 +41,7 @@ class unittest_executed extends \core\event\base {
protected function init() {
$this->data['crud'] = 'u';
- $this->data['level'] = 10;
+ $this->data['level'] = self::LEVEL_PARTICIPATING;
}
public function get_url() {
@@ -116,7 +116,7 @@ class unittest_observer {
class bad_event1 extends \core\event\base {
protected function init() {
//$this->data['crud'] = 'u';
- $this->data['level'] = 10;
+ $this->data['level'] = self::LEVEL_OTHER;
}
}
@@ -127,10 +127,18 @@ class bad_event2 extends \core\event\base {
}
}
+class bad_event2b extends \core\event\base {
+ protected function init() {
+ $this->data['crud'] = 'u';
+ // Invalid level value.
+ $this->data['level'] = -1;
+ }
+}
+
class bad_event3 extends \core\event\base {
protected function init() {
$this->data['crud'] = 'u';
- $this->data['level'] = 10;
+ $this->data['level'] = self::LEVEL_OTHER;
unset($this->data['courseid']);
}
}
@@ -138,7 +146,7 @@ class bad_event3 extends \core\event\base {
class bad_event4 extends \core\event\base {
protected function init() {
$this->data['crud'] = 'u';
- $this->data['level'] = 10;
+ $this->data['level'] = self::LEVEL_OTHER;
$this->data['xxx'] = 1;
}
}
@@ -146,14 +154,14 @@ class bad_event4 extends \core\event\base {
class bad_event5 extends \core\event\base {
protected function init() {
$this->data['crud'] = 'x';
- $this->data['level'] = 10;
+ $this->data['level'] = self::LEVEL_OTHER;
}
}
class bad_event6 extends \core\event\base {
protected function init() {
$this->data['crud'] = 'c';
- $this->data['level'] = 10;
+ $this->data['level'] = self::LEVEL_OTHER;
$this->data['objecttable'] = 'xxx_xxx_xx';
}
}
@@ -161,7 +169,7 @@ class bad_event6 extends \core\event\base {
class bad_event7 extends \core\event\base {
protected function init() {
$this->data['crud'] = 'c';
- $this->data['level'] = 10;
+ $this->data['level'] = self::LEVEL_OTHER;
$this->data['objecttable'] = null;
}
}
@@ -169,14 +177,14 @@ class bad_event7 extends \core\event\base {
class problematic_event1 extends \core\event\base {
protected function init() {
$this->data['crud'] = 'u';
- $this->data['level'] = 10;
+ $this->data['level'] = self::LEVEL_OTHER;
}
}
class problematic_event2 extends \core\event\base {
protected function init() {
$this->data['crud'] = 'c';
- $this->data['level'] = 10;
+ $this->data['level'] = self::LEVEL_OTHER;
$this->context = \context_system::instance();
}
}
@@ -184,7 +192,7 @@ class problematic_event2 extends \core\event\base {
class problematic_event3 extends \core\event\base {
protected function init() {
$this->data['crud'] = 'c';
- $this->data['level'] = 10;
+ $this->data['level'] = self::LEVEL_OTHER;
$this->context = \context_system::instance();
}
@@ -199,7 +207,7 @@ class noname_event extends \core\event\base {
protected function init() {
$this->data['crud'] = 'c';
- $this->data['level'] = 10;
+ $this->data['level'] = self::LEVEL_OTHER;
$this->context = \context_system::instance();
}
}
diff --git a/lib/tests/sessionlib_test.php b/lib/tests/sessionlib_test.php
new file mode 100644
index 00000000000..5f3b0329199
--- /dev/null
+++ b/lib/tests/sessionlib_test.php
@@ -0,0 +1,88 @@
+.
+
+/**
+ * Unit tests for (some of) ../sessionlib.php.
+ *
+ * @package core_session
+ * @category phpunit
+ * @copyright 2103 Rajesh Taneja
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->libdir . '/sessionlib.php');
+
+/**
+ * Unit tests for (some of) ../sessionlib.php.
+ *
+ * @package core_session
+ * @category phpunit
+ * @copyright 2103 Rajesh Taneja
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_sessionlib_testcase extends advanced_testcase {
+
+ /**
+ * Test session_loginas.
+ */
+ public function test_session_loginas() {
+ global $USER;
+ $this->resetAfterTest();
+
+ // Set current user as Admin user and save it for later use.
+ $this->setAdminUser();
+ $adminuser = $USER;
+
+ // Create a new user and try admin loginas this user.
+ $user = $this->getDataGenerator()->create_user();
+ session_loginas($user->id, context_system::instance());
+
+ $this->assertSame($user->id, $USER->id);
+ $this->assertSame(context_system::instance(), $USER->loginascontext);
+ $this->assertSame($adminuser->id, $USER->realuser);
+
+ // Set user as current user and login as admin user in course context.
+ $this->setUser($user);
+ $this->assertNotEquals($adminuser->id, $USER->id);
+ $course = $this->getDataGenerator()->create_course();
+ $coursecontext = context_course::instance($course->id);
+
+ // Catch event triggred.
+ $sink = $this->redirectEvents();
+ session_loginas($adminuser->id, $coursecontext);
+ $events = $sink->get_events();
+ $sink->close();
+ $event = array_pop($events);
+
+ $this->assertSame($adminuser->id, $USER->id);
+ $this->assertSame($coursecontext, $USER->loginascontext);
+ $this->assertSame($user->id, $USER->realuser);
+
+ // Test event captured has proper information.
+ $this->assertInstanceOf('\core\event\user_loggedinas', $event);
+ $this->assertSame($user->id, $event->objectid);
+ $this->assertSame($adminuser->id, $event->relateduserid);
+ $this->assertSame($course->id, $event->courseid);
+ $this->assertEquals($coursecontext, $event->get_context());
+ $oldfullname = fullname($user, true);
+ $newfullname = fullname($adminuser, true);
+ $expectedlogdata = array($course->id, "course", "loginas", "../user/view.php?id=$course->id&user=$user->id", "$oldfullname -> $newfullname");
+ $this->assertEventLegacyLogData($expectedlogdata, $event);
+ }
+}
diff --git a/lib/tests/statslib_test.php b/lib/tests/statslib_test.php
index fe9728caf06..50fd289d7f5 100644
--- a/lib/tests/statslib_test.php
+++ b/lib/tests/statslib_test.php
@@ -299,24 +299,22 @@ class core_statslib_testcase extends advanced_testcase {
* Test progress output when debug is on.
*/
public function test_statslib_progress_debug() {
- global $CFG;
-
- $CFG->debug = DEBUG_ALL;
+ set_debugging(DEBUG_ALL);
$this->expectOutputString('1:0 ');
stats_progress('init');
stats_progress('1');
+ $this->resetDebugging();
}
/**
* Test progress output when debug is off.
*/
public function test_statslib_progress_no_debug() {
- global $CFG;
-
- $CFG->debug = DEBUG_NONE;
+ set_debugging(DEBUG_NONE);
$this->expectOutputString('.');
stats_progress('init');
stats_progress('1');
+ $this->resetDebugging();
}
/**
diff --git a/lib/tests/weblib_test.php b/lib/tests/weblib_test.php
index e335f22c07a..a43dfc6ca7e 100644
--- a/lib/tests/weblib_test.php
+++ b/lib/tests/weblib_test.php
@@ -428,4 +428,44 @@ class core_weblib_testcase extends advanced_testcase {
$this->assertSame("do\n re\n mi\n", $trace2->get_buffer());
$this->expectOutputString('');
}
+
+ public function test_set_debugging() {
+ global $CFG;
+
+ $this->resetAfterTest();
+
+ $this->assertEquals(DEBUG_DEVELOPER, $CFG->debug);
+ $this->assertTrue($CFG->debugdeveloper);
+ $this->assertNotEmpty($CFG->debugdisplay);
+
+ set_debugging(DEBUG_DEVELOPER, true);
+ $this->assertEquals(DEBUG_DEVELOPER, $CFG->debug);
+ $this->assertTrue($CFG->debugdeveloper);
+ $this->assertNotEmpty($CFG->debugdisplay);
+
+ set_debugging(DEBUG_DEVELOPER, false);
+ $this->assertEquals(DEBUG_DEVELOPER, $CFG->debug);
+ $this->assertTrue($CFG->debugdeveloper);
+ $this->assertEmpty($CFG->debugdisplay);
+
+ set_debugging(-1);
+ $this->assertEquals(-1, $CFG->debug);
+ $this->assertTrue($CFG->debugdeveloper);
+
+ set_debugging(DEBUG_ALL);
+ $this->assertEquals(DEBUG_ALL, $CFG->debug);
+ $this->assertFalse($CFG->debugdeveloper);
+
+ set_debugging(DEBUG_NORMAL);
+ $this->assertEquals(DEBUG_NORMAL, $CFG->debug);
+ $this->assertFalse($CFG->debugdeveloper);
+
+ set_debugging(DEBUG_MINIMAL);
+ $this->assertEquals(DEBUG_MINIMAL, $CFG->debug);
+ $this->assertFalse($CFG->debugdeveloper);
+
+ set_debugging(DEBUG_NONE);
+ $this->assertEquals(DEBUG_NONE, $CFG->debug);
+ $this->assertFalse($CFG->debugdeveloper);
+ }
}
diff --git a/lib/upgrade.txt b/lib/upgrade.txt
index f7fc350bebb..29a297f941f 100644
--- a/lib/upgrade.txt
+++ b/lib/upgrade.txt
@@ -16,6 +16,8 @@ information provided here is intended especially for developers.
* The string manager classes were renamed. Note that they should not be modified or used directly,
always use get_string_manager() to get instance of the string manager.
* The ability to use an 'insecure' rc4encrypt/rc4decrypt key has been removed.
+* Use $CFG->debugdeveloper instead of debugging('', DEBUG_DEVELOPER).
+* Use set_debugging(DEBUG_xxx) when changing debugging level for current request.
DEPRECATIONS:
Various previously deprecated functions have now been altered to throw DEBUG_DEVELOPER debugging notices
diff --git a/lib/upgradelib.php b/lib/upgradelib.php
index e49b9568f5f..0ca4493b39c 100644
--- a/lib/upgradelib.php
+++ b/lib/upgradelib.php
@@ -488,8 +488,6 @@ function upgrade_plugins($type, $startcallback, $endcallback, $verbose) {
message_update_processors($plug);
}
upgrade_plugin_mnet_functions($component);
- cache_helper::purge_all(true);
- purge_all_caches();
$endcallback($component, true, $verbose);
} else if ($installedversion < $plugin->version) { // upgrade
@@ -521,8 +519,6 @@ function upgrade_plugins($type, $startcallback, $endcallback, $verbose) {
message_update_processors($plug);
}
upgrade_plugin_mnet_functions($component);
- cache_helper::purge_all(true);
- purge_all_caches();
$endcallback($component, false, $verbose);
} else if ($installedversion > $plugin->version) {
@@ -653,7 +649,6 @@ function upgrade_plugins_modules($startcallback, $endcallback, $verbose) {
message_update_providers($component);
upgrade_plugin_mnet_functions($component);
- purge_all_caches();
$endcallback($component, true, $verbose);
} else if ($currmodule->version < $module->version) {
@@ -687,8 +682,6 @@ function upgrade_plugins_modules($startcallback, $endcallback, $verbose) {
message_update_providers($component);
upgrade_plugin_mnet_functions($component);
- purge_all_caches();
-
$endcallback($component, false, $verbose);
} else if ($currmodule->version > $module->version) {
@@ -844,7 +837,6 @@ function upgrade_plugins_blocks($startcallback, $endcallback, $verbose) {
message_update_providers($component);
upgrade_plugin_mnet_functions($component);
- purge_all_caches();
$endcallback($component, true, $verbose);
} else if ($currblock->version < $block->version) {
@@ -877,7 +869,6 @@ function upgrade_plugins_blocks($startcallback, $endcallback, $verbose) {
message_update_providers($component);
upgrade_plugin_mnet_functions($component);
- purge_all_caches();
$endcallback($component, false, $verbose);
} else if ($currblock->version > $block->version) {
@@ -1148,7 +1139,7 @@ function upgrade_handle_exception($ex, $plugin = null) {
upgrade_log(UPGRADE_LOG_ERROR, $plugin, 'Exception: ' . get_class($ex), $info->message, $info->backtrace);
// Always turn on debugging - admins need to know what is going on
- $CFG->debug = DEBUG_DEVELOPER;
+ set_debugging(DEBUG_DEVELOPER, true);
default_exception_handler($ex, true, $plugin);
}
@@ -1532,9 +1523,9 @@ function upgrade_core($version, $verbose) {
require_once($CFG->libdir.'/db/upgrade.php'); // Defines upgrades
try {
- // Reset caches before any output
- purge_all_caches();
+ // Reset caches before any output.
cache_helper::purge_all(true);
+ purge_all_caches();
// Upgrade current language pack if we can
upgrade_language_pack();
@@ -1566,8 +1557,8 @@ function upgrade_core($version, $verbose) {
cache_helper::update_definitions(true);
// Purge caches again, just to be sure we arn't holding onto old stuff now.
- purge_all_caches();
cache_helper::purge_all(true);
+ purge_all_caches();
// Clean up contexts - more and more stuff depends on existence of paths and contexts
context_helper::cleanup_instances();
@@ -1594,6 +1585,10 @@ function upgrade_noncore($verbose) {
// upgrade all plugins types
try {
+ // Reset caches before any output.
+ cache_helper::purge_all(true);
+ purge_all_caches();
+
$plugintypes = core_component::get_plugin_types();
foreach ($plugintypes as $type=>$location) {
upgrade_plugins($type, 'print_upgrade_part_start', 'print_upgrade_part_end', $verbose);
@@ -1602,6 +1597,11 @@ function upgrade_noncore($verbose) {
cache_helper::update_definitions();
// Mark the site as upgraded.
set_config('allversionshash', core_component::get_all_versions_hash());
+
+ // Purge caches again, just to be sure we arn't holding onto old stuff now.
+ cache_helper::purge_all(true);
+ purge_all_caches();
+
} catch (Exception $ex) {
upgrade_handle_exception($ex);
}
diff --git a/lib/weblib.php b/lib/weblib.php
index ee1d160bdd4..fdde141ad5f 100644
--- a/lib/weblib.php
+++ b/lib/weblib.php
@@ -1216,7 +1216,7 @@ function format_text($text, $format = FORMAT_MOODLE, $options = null, $courseidd
// the text before storing into database which would be itself big bug..
$text = str_replace("\"$CFG->httpswwwroot/draftfile.php", "\"$CFG->httpswwwroot/brokenfile.php#", $text);
- if (debugging('', DEBUG_DEVELOPER)) {
+ if ($CFG->debugdeveloper) {
if (strpos($text, '@@PLUGINFILE@@/') !== false) {
debugging('Before calling format_text(), the content must be processed with file_rewrite_pluginfile_urls()',
DEBUG_DEVELOPER);
@@ -2791,6 +2791,24 @@ function print_tabs($tabrows, $selected = null, $inactive = null, $activated = n
}
}
+/**
+ * Alter debugging level for the current request,
+ * the change is not saved in database.
+ *
+ * @param int $level one of the DEBUG_* constants
+ * @param bool $debugdisplay
+ */
+function set_debugging($level, $debugdisplay = null) {
+ global $CFG;
+
+ $CFG->debug = (int)$level;
+ $CFG->debugdeveloper = (($CFG->debug & DEBUG_DEVELOPER) === DEBUG_DEVELOPER);
+
+ if ($debugdisplay !== null) {
+ $CFG->debugdisplay = (bool)$debugdisplay;
+ }
+}
+
/**
* Standard Debugging Function
*
diff --git a/lib/yui/build/moodle-core-blocks/moodle-core-blocks-debug.js b/lib/yui/build/moodle-core-blocks/moodle-core-blocks-debug.js
index ea694d02bb2..e2b34dfbced 100644
--- a/lib/yui/build/moodle-core-blocks/moodle-core-blocks-debug.js
+++ b/lib/yui/build/moodle-core-blocks/moodle-core-blocks-debug.js
@@ -115,7 +115,7 @@ Y.extend(DRAGBLOCK, M.core.dragdrop, {
blocklist.each(function(blocknode) {
var move = blocknode.one('a.'+CSS.EDITINGMOVE);
if (move) {
- move.remove();
+ move.replace(this.get_drag_handle(move.getAttribute('title'), '', 'icon', true));
blocknode.one('.'+CSS.HEADER).setStyle('cursor', 'move');
}
}, this);
@@ -379,7 +379,8 @@ M.core.blockdraganddrop.init = function(params) {
M.core_blocks = M.core_blocks || {};
M.core_blocks.init_dragdrop = function(params) {
M.core.blockdraganddrop.init(params);
-};/**
+};
+/**
* This file contains the drag and drop manager class.
*
* Provides drag and drop functionality for blocks.
@@ -492,7 +493,7 @@ MANAGER.prototype = {
// This is VERY important as without it dnd won't work for empty block regions.
dragdelegation.on('drag:mouseDown', this.enable_all_regions, this);
- region.remove_block_move_icons();
+ region.change_block_move_icons(this);
}
Y.log('Initialisation of drag and drop for blocks complete.', 'info');
},
@@ -771,7 +772,8 @@ Y.extend(MANAGER, M.core.dragdrop, MANAGER.prototype, {
value : []
}
}
-});/**
+});
+/**
* This file contains the Block Region class used by the drag and drop manager.
*
* Provides drag and drop functionality for blocks.
@@ -861,13 +863,19 @@ BLOCKREGION.prototype = {
},
/**
- * Removes the move icons and changes the cursor to a move icon when over the header.
- * @method remove_block_move_icons
+ * Change the move icons to enhanced drag handles and changes the cursor to a move icon when over the header.
+ * @param M.core.dragdrop the block manager
+ * @method change_block_move_icons
*/
- remove_block_move_icons : function() {
+ change_block_move_icons : function(manager) {
+ var handle, icon;
this.get('node').all('.'+CSS.BLOCK+' a.'+CSS.EDITINGMOVE).each(function(moveicon){
moveicon.ancestor('.'+CSS.BLOCK).one('.'+CSS.HEADER).setStyle('cursor', 'move');
- moveicon.remove();
+ handle = manager.get_drag_handle(moveicon.getAttribute('title'), '', 'icon', true);
+ icon = handle.one('img');
+ icon.addClass('iconsmall');
+ icon.removeClass('icon');
+ moveicon.replace(handle);
});
},
@@ -988,6 +996,7 @@ Y.extend(BLOCKREGION, Y.Base, BLOCKREGION.prototype, {
}
});
+
}, '@VERSION@', {
"requires": [
"base",
diff --git a/lib/yui/build/moodle-core-blocks/moodle-core-blocks-min.js b/lib/yui/build/moodle-core-blocks/moodle-core-blocks-min.js
index 31b92ae98da..fb966a010b5 100644
--- a/lib/yui/build/moodle-core-blocks/moodle-core-blocks-min.js
+++ b/lib/yui/build/moodle-core-blocks/moodle-core-blocks-min.js
@@ -1,2 +1,2 @@
-YUI.add("moodle-core-blocks",function(e,t){var n="/lib/ajax/blocks.php",r={BLOCK:"block",BLOCKREGION:"block-region",BLOCKADMINBLOCK:"block_adminblock",EDITINGMOVE:"editing_move",HEADER:"header",LIGHTBOX:"lightbox",REGIONCONTENT:"region-content",SKIPBLOCK:"skip-block",SKIPBLOCKTO:"skip-block-to",MYINDEX:"page-my-index",REGIONMAIN:"region-main"},i=function(){i.superclass.constructor.apply(this,arguments)};e.extend(i,M.core.dragdrop,{skipnodetop:null,skipnodebottom:null,dragsourceregion:null,initializer:function(){this.groups=["block"],this.samenodeclass=r.BLOCK,this.parentnodeclass=r.REGIONCONTENT;var t=e.Node.all("body#"+r.MYINDEX+" #"+r.REGIONMAIN+" > ."+r.REGIONCONTENT);if(t.size()>0){var n=t.item(0);n.addClass(r.BLOCKREGION),n.set("id",r.REGIONCONTENT),n.one("div").addClass(r.REGIONCONTENT)}var i=e.Node.all("div."+r.BLOCKREGION);if(i.size()===0)return!1;if(i.size()!=this.get("regions").length){var s=e.Node.create("").addClass(r.BLOCKREGION),o=e.Node.create("").addClass(r.REGIONCONTENT);s.appendChild(o);var u=i.filter("#region-pre"),a=i.filter("#region-post");u.size()===0&&a.size()===1?(s.setAttrs({id:"region-pre"}),a.item(0).insert(s,"before"),i.unshift(s)):a.size()===0&&u.size()===1&&(s.setAttrs({id:"region-post"}),u.item(0).insert(s,"after"),i.push(s))}i.each(function(t){var n=new e.DD.Drop({node:t.one("div."+r.REGIONCONTENT),groups:this.groups,padding:"40 240 40 240"}),i=new e.DD.Delegate({container:t,nodes:"."+r.BLOCK,target:!0,handles:["."+r.HEADER],invalid:".block-hider-hide, .block-hider-show, .moveto",dragConfig:{groups:this.groups}});i.dd.plug(e.Plugin.DDProxy,{moveOnEnd:!1}),i.dd.plug(e.Plugin.DDWinScroll);var s=t.all("."+r.BLOCK);s.each(function(e){var t=e.one("a."+r.EDITINGMOVE);t&&(t.remove(),e.one("."+r.HEADER).setStyle("cursor","move"))},this)},this)},get_block_id:function(e){return Number(e.get("id").replace(/inst/i,""))},get_block_region:function(t){var n=t.ancestor("div."+r.BLOCKREGION).get("id").replace(/region-/i,"");return e.Array.indexOf(this.get("regions"),n)===-1?(right_to_left()&&(n==="post"?n="pre":n==="pre"&&(n="post")),"side-"+n):n},get_region_id:function(e){return e.get("id").replace(/region-/i,"")},drag_start:function(e){var t=e.target;this.dragsourceregion=t.get("node").ancestor("div."+r.BLOCKREGION),t.get("node").previous()&&t.get("node").previous().hasClass(r.SKIPBLOCK)&&(this.skipnodetop=t.get("node").previous()),t.get("node").next()&&t.get("node").next().hasClass(r.SKIPBLOCKTO)&&(this.skipnodebottom=t.get("node").next())},drop_over:function(t){var n=t.drag.get("node"),i=t.drop.get("node");i.hasClass(this.parentnodeclass)&&i.one("."+r.BLOCKADMINBLOCK)&&i.one("."+r.BLOCKADMINBLOCK).next("."+r.BLOCK)&&i.prepend(n);if(this.dragsourceregion.contains(i))return!1;var s=e.one("body"),o=this.get_region_id(this.dragsourceregion);s.hasClass("side-"+o+"-only")&&s.removeClass("side-"+o+"-only"),o=this.get_region_id(i.ancestor("div."+r.BLOCKREGION)),this.dragsourceregion.all("."+r.BLOCK).size()==0&&this.dragsourceregion.get("id").match(/(region-pre|region-post)/i)&&(s.hasClass("side-"+o+"-only")||s.addClass("side-"+o+"-only"))},drop_end:function(){this.skipnodetop=null,this.skipnodebottom=null,this.dragsourceregion=null},drag_dropmiss:function(e){this.drop_hit(e)},drop_hit:function(t){var i=t.drag,s=i.get("node"),o=t.drop.get("node");s.previous()&&s.previous().hasClass(r.SKIPBLOCK)&&s.insert(s.previous(),"after"),this.skipnodetop&&s.insert(this.skipnodetop,"before"),this.skipnodebottom&&s.insert(this.skipnodebottom,"after");var u=M.util.add_lightbox(e,s),a={sesskey:M.cfg.sesskey,courseid:this.get("courseid"),pagelayout:this.get("pagelayout"),pagetype:this.get("pagetype"),subpage:this.get("subpage"),contextid:this.get("contextid"),action:"move",bui_moveid:this.get_block_id(s),bui_newregion:this.get_block_region(o)};this.get("cmid")&&(a.cmid=this.get("cmid")),s.next("."+this.samenodeclass)&&!s.next("."+this.samenodeclass).hasClass(r.BLOCKADMINBLOCK)&&(a.bui_beforeid=this.get_block_id(s.next("."+this.samenodeclass))),e.io(M.cfg.wwwroot+n,{method:"POST",data:a,on:{start:function(){u.show()},success:function(t,n){window.setTimeout(function(){u.hide()},250);try{var r=e.JSON.parse(n.responseText);r.error&&new M.core.ajaxException(r)}catch(i){}},failure:function(e,t){this.ajax_failure(t),u.hide()}},context:this})}},{NAME:"core-blocks-dragdrop",ATTRS:{courseid:{value:null},cmid:{value:null},contextid:{value:null},pagelayout:{value:null},pagetype:{value:null},subpage:{value:null},regions:{value:null}}}),M.core=M.core||{},M.core.blockdraganddrop=M.core.blockdraganddrop||{},M.core.blockdraganddrop._isusingnewblocksmethod=null,M.core.blockdraganddrop.is_using_blocks_render_method=function(){if(this._isusingnewblocksmethod===null){var t=e.all(".block-region[data-blockregion]").size(),n=e.all(".block-region").size();this._isusingnewblocksmethod=n===t}return this._isusingnewblocksmethod},M.core.blockdraganddrop.init=function(e){this.is_using_blocks_render_method()?new s(e):new i(e)},M.core_blocks=M.core_blocks||{},M.core_blocks.init_dragdrop=function(e){M.core.blockdraganddrop.init(e)};var s=function(){s.superclass.constructor.apply(this,arguments)};s.prototype={skipnodetop:null,skipnodebottom:null,regionobjects:{},initializer:function(){var t=this.get("regions"),n=0,i,s,u,a;this.groups=["block"],this.samenodeclass=r.BLOCK,this.parentnodeclass=r.BLOCKREGION;var f=e.Node.all("body#"+r.MYINDEX+" #"+r.REGIONMAIN+" > ."+r.REGIONCONTENT);if(f.size()>0){var l=f.item(0);l.addClass(r.BLOCKREGION),l.set("id",r.REGIONCONTENT),l.one("div").addClass(r.REGIONCONTENT)}for(n in t)s=t[n],i=new o({manager:this,region:s,node:e.one("#block-region-"+s)}),this.regionobjects[s]=i,u=new e.DD.Drop({node:i.get_droptarget(),groups:this.groups,padding:"40 240 40 240"}),a=new e.DD.Delegate({container:i.get_droptarget(),nodes:"."+r.BLOCK,target:!0,handles:["."+r.HEADER],invalid:".block-hider-hide, .block-hider-show, .moveto",dragConfig:{groups:this.groups}}),a.dd.plug(e.Plugin.DDProxy,{moveOnEnd
-:!1}),a.dd.plug(e.Plugin.DDWinScroll),a.on("drag:mouseDown",this.enable_all_regions,this),i.remove_block_move_icons()},get_block_id:function(e){return Number(e.get("id").replace(/inst/i,""))},get_block_region:function(e){return e.test("[data-blockregion]")||(e=e.ancestor("[data-blockregion]")),e.getData("blockregion")},get_region_object:function(e){return this.regionobjects[this.get_block_region(e)]},enable_all_regions:function(){var e=0;for(e in this.regionobjects)this.regionobjects[e].enable()},disable_regions_if_required:function(){var e=0;for(e in this.regionobjects)this.regionobjects[e].disable_if_required()},drag_start:function(e){var t=e.target;t.get("node").previous()&&t.get("node").previous().hasClass(r.SKIPBLOCK)&&(this.skipnodetop=t.get("node").previous()),t.get("node").next()&&t.get("node").next().hasClass(r.SKIPBLOCKTO)&&(this.skipnodebottom=t.get("node").next())},drop_over:function(e){var t=e.drag.get("node"),n=e.drop.get("node");n.hasClass(r.REGIONCONTENT)&&n.one("."+r.BLOCKADMINBLOCK)&&n.one("."+r.BLOCKADMINBLOCK).next("."+r.BLOCK)&&n.prepend(t)},drop_end:function(){this.skipnodetop=null,this.skipnodebottom=null,this.disable_regions_if_required()},drag_dropmiss:function(e){this.drop_hit(e)},drop_hit:function(t){var i=t.drag.get("node"),s=t.drop.get("node");i.previous()&&i.previous().hasClass(r.SKIPBLOCK)&&i.insert(i.previous(),"after"),this.skipnodetop&&i.insert(this.skipnodetop,"before"),this.skipnodebottom&&i.insert(this.skipnodebottom,"after");var o=M.util.add_lightbox(e,i),u={sesskey:M.cfg.sesskey,courseid:this.get("courseid"),pagelayout:this.get("pagelayout"),pagetype:this.get("pagetype"),subpage:this.get("subpage"),contextid:this.get("contextid"),action:"move",bui_moveid:this.get_block_id(i),bui_newregion:this.get_block_region(s)};this.get("cmid")&&(u.cmid=this.get("cmid")),i.next("."+r.BLOCK)&&!i.next("."+r.BLOCK).hasClass(r.BLOCKADMINBLOCK)&&(u.bui_beforeid=this.get_block_id(i.next("."+r.BLOCK))),e.io(M.cfg.wwwroot+n,{method:"POST",data:u,on:{start:function(){o.show()},success:function(t,n){window.setTimeout(function(){o.hide()},250);try{var r=e.JSON.parse(n.responseText);r.error&&new M.core.ajaxException(r)}catch(i){}},failure:function(e,t){this.ajax_failure(t),o.hide()},complete:function(){this.disable_regions_if_required()}},context:this})}},e.extend(s,M.core.dragdrop,s.prototype,{NAME:"core-blocks-dragdrop-manager",ATTRS:{courseid:{value:null},cmid:{value:null},contextid:{value:null},pagelayout:{value:null},pagetype:{value:null},subpage:{value:null},regions:{value:[]}}});var o=function(){o.superclass.constructor.apply(this,arguments)};o.prototype={initializer:function(){var t=this.get("node");t||this.create_and_add_node();var n=e.one("body"),i=t.all("."+r.BLOCK).size()>0,s=this.get_has_region_class();this.set("hasblocks",i),n.hasClass(s)||n.addClass(s),n.addClass(i?this.get_used_region_class():this.get_empty_region_class()),n.removeClass(i?this.get_empty_region_class():this.get_used_region_class())},create_and_add_node:function(){var t=e.Node.create,n=this.get("region"),i=t('').addClass(r.BLOCKREGION).setData("blockregion",n),s=this.get("manager").get("regions"),o,u=!1,a=!1,f=!1,l,c;for(o in s)s[o].match(/(pre|left)/)?u=s[o]:s[o].match(/(post|right)/)&&(a=s[o]);u!==!1&&a!==!1&&(n===u?(c=e.one("#block-region-"+a),c&&(c.insert(i,"before"),f=!0)):(l=e.one("#block-region-"+u),l&&(l.insert(i,"after"),f=!0))),f===!1&&e.one("body").append(i),this.set("node",i)},remove_block_move_icons:function(){this.get("node").all("."+r.BLOCK+" a."+r.EDITINGMOVE).each(function(e){e.ancestor("."+r.BLOCK).one("."+r.HEADER).setStyle("cursor","move"),e.remove()})},get_has_region_class:function(){return"has-region-"+this.get("region")},get_empty_region_class:function(){return"empty-region-"+this.get("region")},get_used_region_class:function(){return"used-region-"+this.get("region")},get_droptarget:function(){var e=this.get("node");return e.test('[data-droptarget="1"]')?e:e.one('[data-droptarget="1"]')},enable:function(){e.one("body").addClass(this.get_used_region_class()).removeClass(this.get_empty_region_class())},disable_if_required:function(){this.get("node").all("."+r.BLOCK).size()===0&&e.one("body").addClass(this.get_empty_region_class()).removeClass(this.get_used_region_class())}},e.extend(o,e.Base,o.prototype,{NAME:"core-blocks-dragdrop-blockregion",ATTRS:{manager:{writeOnce:"initOnly",validator:function(t){return e.Lang.isObject(t)&&t instanceof s}},region:{writeOnce:"initOnly",validator:function(t){return e.Lang.isString(t)}},node:{validator:function(t){return e.Lang.isObject(t)||e.Lang.isNull(t)}},hasblocks:{value:!1,validator:function(t){return e.Lang.isBoolean(t)}}}})},"@VERSION@",{requires:["base","node","io","dom","dd","dd-scroll","moodle-core-dragdrop","moodle-core-notification"]});
+YUI.add("moodle-core-blocks",function(e,t){var n="/lib/ajax/blocks.php",r={BLOCK:"block",BLOCKREGION:"block-region",BLOCKADMINBLOCK:"block_adminblock",EDITINGMOVE:"editing_move",HEADER:"header",LIGHTBOX:"lightbox",REGIONCONTENT:"region-content",SKIPBLOCK:"skip-block",SKIPBLOCKTO:"skip-block-to",MYINDEX:"page-my-index",REGIONMAIN:"region-main"},i=function(){i.superclass.constructor.apply(this,arguments)};e.extend(i,M.core.dragdrop,{skipnodetop:null,skipnodebottom:null,dragsourceregion:null,initializer:function(){this.groups=["block"],this.samenodeclass=r.BLOCK,this.parentnodeclass=r.REGIONCONTENT;var t=e.Node.all("body#"+r.MYINDEX+" #"+r.REGIONMAIN+" > ."+r.REGIONCONTENT);if(t.size()>0){var n=t.item(0);n.addClass(r.BLOCKREGION),n.set("id",r.REGIONCONTENT),n.one("div").addClass(r.REGIONCONTENT)}var i=e.Node.all("div."+r.BLOCKREGION);if(i.size()===0)return!1;if(i.size()!=this.get("regions").length){var s=e.Node.create("").addClass(r.BLOCKREGION),o=e.Node.create("").addClass(r.REGIONCONTENT);s.appendChild(o);var u=i.filter("#region-pre"),a=i.filter("#region-post");u.size()===0&&a.size()===1?(s.setAttrs({id:"region-pre"}),a.item(0).insert(s,"before"),i.unshift(s)):a.size()===0&&u.size()===1&&(s.setAttrs({id:"region-post"}),u.item(0).insert(s,"after"),i.push(s))}i.each(function(t){var n=new e.DD.Drop({node:t.one("div."+r.REGIONCONTENT),groups:this.groups,padding:"40 240 40 240"}),i=new e.DD.Delegate({container:t,nodes:"."+r.BLOCK,target:!0,handles:["."+r.HEADER],invalid:".block-hider-hide, .block-hider-show, .moveto",dragConfig:{groups:this.groups}});i.dd.plug(e.Plugin.DDProxy,{moveOnEnd:!1}),i.dd.plug(e.Plugin.DDWinScroll);var s=t.all("."+r.BLOCK);s.each(function(e){var t=e.one("a."+r.EDITINGMOVE);t&&(t.replace(this.get_drag_handle(t.getAttribute("title"),"","icon",!0)),e.one("."+r.HEADER).setStyle("cursor","move"))},this)},this)},get_block_id:function(e){return Number(e.get("id").replace(/inst/i,""))},get_block_region:function(t){var n=t.ancestor("div."+r.BLOCKREGION).get("id").replace(/region-/i,"");return e.Array.indexOf(this.get("regions"),n)===-1?(right_to_left()&&(n==="post"?n="pre":n==="pre"&&(n="post")),"side-"+n):n},get_region_id:function(e){return e.get("id").replace(/region-/i,"")},drag_start:function(e){var t=e.target;this.dragsourceregion=t.get("node").ancestor("div."+r.BLOCKREGION),t.get("node").previous()&&t.get("node").previous().hasClass(r.SKIPBLOCK)&&(this.skipnodetop=t.get("node").previous()),t.get("node").next()&&t.get("node").next().hasClass(r.SKIPBLOCKTO)&&(this.skipnodebottom=t.get("node").next())},drop_over:function(t){var n=t.drag.get("node"),i=t.drop.get("node");i.hasClass(this.parentnodeclass)&&i.one("."+r.BLOCKADMINBLOCK)&&i.one("."+r.BLOCKADMINBLOCK).next("."+r.BLOCK)&&i.prepend(n);if(this.dragsourceregion.contains(i))return!1;var s=e.one("body"),o=this.get_region_id(this.dragsourceregion);s.hasClass("side-"+o+"-only")&&s.removeClass("side-"+o+"-only"),o=this.get_region_id(i.ancestor("div."+r.BLOCKREGION)),this.dragsourceregion.all("."+r.BLOCK).size()==0&&this.dragsourceregion.get("id").match(/(region-pre|region-post)/i)&&(s.hasClass("side-"+o+"-only")||s.addClass("side-"+o+"-only"))},drop_end:function(){this.skipnodetop=null,this.skipnodebottom=null,this.dragsourceregion=null},drag_dropmiss:function(e){this.drop_hit(e)},drop_hit:function(t){var i=t.drag,s=i.get("node"),o=t.drop.get("node");s.previous()&&s.previous().hasClass(r.SKIPBLOCK)&&s.insert(s.previous(),"after"),this.skipnodetop&&s.insert(this.skipnodetop,"before"),this.skipnodebottom&&s.insert(this.skipnodebottom,"after");var u=M.util.add_lightbox(e,s),a={sesskey:M.cfg.sesskey,courseid:this.get("courseid"),pagelayout:this.get("pagelayout"),pagetype:this.get("pagetype"),subpage:this.get("subpage"),contextid:this.get("contextid"),action:"move",bui_moveid:this.get_block_id(s),bui_newregion:this.get_block_region(o)};this.get("cmid")&&(a.cmid=this.get("cmid")),s.next("."+this.samenodeclass)&&!s.next("."+this.samenodeclass).hasClass(r.BLOCKADMINBLOCK)&&(a.bui_beforeid=this.get_block_id(s.next("."+this.samenodeclass))),e.io(M.cfg.wwwroot+n,{method:"POST",data:a,on:{start:function(){u.show()},success:function(t,n){window.setTimeout(function(){u.hide()},250);try{var r=e.JSON.parse(n.responseText);r.error&&new M.core.ajaxException(r)}catch(i){}},failure:function(e,t){this.ajax_failure(t),u.hide()}},context:this})}},{NAME:"core-blocks-dragdrop",ATTRS:{courseid:{value:null},cmid:{value:null},contextid:{value:null},pagelayout:{value:null},pagetype:{value:null},subpage:{value:null},regions:{value:null}}}),M.core=M.core||{},M.core.blockdraganddrop=M.core.blockdraganddrop||{},M.core.blockdraganddrop._isusingnewblocksmethod=null,M.core.blockdraganddrop.is_using_blocks_render_method=function(){if(this._isusingnewblocksmethod===null){var t=e.all(".block-region[data-blockregion]").size(),n=e.all(".block-region").size();this._isusingnewblocksmethod=n===t}return this._isusingnewblocksmethod},M.core.blockdraganddrop.init=function(e){this.is_using_blocks_render_method()?new s(e):new i(e)},M.core_blocks=M.core_blocks||{},M.core_blocks.init_dragdrop=function(e){M.core.blockdraganddrop.init(e)};var s=function(){s.superclass.constructor.apply(this,arguments)};s.prototype={skipnodetop:null,skipnodebottom:null,regionobjects:{},initializer:function(){var t=this.get("regions"),n=0,i,s,u,a;this.groups=["block"],this.samenodeclass=r.BLOCK,this.parentnodeclass=r.BLOCKREGION;var f=e.Node.all("body#"+r.MYINDEX+" #"+r.REGIONMAIN+" > ."+r.REGIONCONTENT);if(f.size()>0){var l=f.item(0);l.addClass(r.BLOCKREGION),l.set("id",r.REGIONCONTENT),l.one("div").addClass(r.REGIONCONTENT)}for(n in t)s=t[n],i=new o({manager:this,region:s,node:e.one("#block-region-"+s)}),this.regionobjects[s]=i,u=new e.DD.Drop({node:i.get_droptarget(),groups:this.groups,padding:"40 240 40 240"}),a=new e.DD.Delegate({container:i.get_droptarget(),nodes:"."+r.BLOCK,target:!0,handles:["."+r.HEADER],invalid:".block-hider-hide, .block-hider-show, .moveto",dragConfig
+:{groups:this.groups}}),a.dd.plug(e.Plugin.DDProxy,{moveOnEnd:!1}),a.dd.plug(e.Plugin.DDWinScroll),a.on("drag:mouseDown",this.enable_all_regions,this),i.change_block_move_icons(this)},get_block_id:function(e){return Number(e.get("id").replace(/inst/i,""))},get_block_region:function(e){return e.test("[data-blockregion]")||(e=e.ancestor("[data-blockregion]")),e.getData("blockregion")},get_region_object:function(e){return this.regionobjects[this.get_block_region(e)]},enable_all_regions:function(){var e=0;for(e in this.regionobjects)this.regionobjects[e].enable()},disable_regions_if_required:function(){var e=0;for(e in this.regionobjects)this.regionobjects[e].disable_if_required()},drag_start:function(e){var t=e.target;t.get("node").previous()&&t.get("node").previous().hasClass(r.SKIPBLOCK)&&(this.skipnodetop=t.get("node").previous()),t.get("node").next()&&t.get("node").next().hasClass(r.SKIPBLOCKTO)&&(this.skipnodebottom=t.get("node").next())},drop_over:function(e){var t=e.drag.get("node"),n=e.drop.get("node");n.hasClass(r.REGIONCONTENT)&&n.one("."+r.BLOCKADMINBLOCK)&&n.one("."+r.BLOCKADMINBLOCK).next("."+r.BLOCK)&&n.prepend(t)},drop_end:function(){this.skipnodetop=null,this.skipnodebottom=null,this.disable_regions_if_required()},drag_dropmiss:function(e){this.drop_hit(e)},drop_hit:function(t){var i=t.drag.get("node"),s=t.drop.get("node");i.previous()&&i.previous().hasClass(r.SKIPBLOCK)&&i.insert(i.previous(),"after"),this.skipnodetop&&i.insert(this.skipnodetop,"before"),this.skipnodebottom&&i.insert(this.skipnodebottom,"after");var o=M.util.add_lightbox(e,i),u={sesskey:M.cfg.sesskey,courseid:this.get("courseid"),pagelayout:this.get("pagelayout"),pagetype:this.get("pagetype"),subpage:this.get("subpage"),contextid:this.get("contextid"),action:"move",bui_moveid:this.get_block_id(i),bui_newregion:this.get_block_region(s)};this.get("cmid")&&(u.cmid=this.get("cmid")),i.next("."+r.BLOCK)&&!i.next("."+r.BLOCK).hasClass(r.BLOCKADMINBLOCK)&&(u.bui_beforeid=this.get_block_id(i.next("."+r.BLOCK))),e.io(M.cfg.wwwroot+n,{method:"POST",data:u,on:{start:function(){o.show()},success:function(t,n){window.setTimeout(function(){o.hide()},250);try{var r=e.JSON.parse(n.responseText);r.error&&new M.core.ajaxException(r)}catch(i){}},failure:function(e,t){this.ajax_failure(t),o.hide()},complete:function(){this.disable_regions_if_required()}},context:this})}},e.extend(s,M.core.dragdrop,s.prototype,{NAME:"core-blocks-dragdrop-manager",ATTRS:{courseid:{value:null},cmid:{value:null},contextid:{value:null},pagelayout:{value:null},pagetype:{value:null},subpage:{value:null},regions:{value:[]}}});var o=function(){o.superclass.constructor.apply(this,arguments)};o.prototype={initializer:function(){var t=this.get("node");t||this.create_and_add_node();var n=e.one("body"),i=t.all("."+r.BLOCK).size()>0,s=this.get_has_region_class();this.set("hasblocks",i),n.hasClass(s)||n.addClass(s),n.addClass(i?this.get_used_region_class():this.get_empty_region_class()),n.removeClass(i?this.get_empty_region_class():this.get_used_region_class())},create_and_add_node:function(){var t=e.Node.create,n=this.get("region"),i=t('').addClass(r.BLOCKREGION).setData("blockregion",n),s=this.get("manager").get("regions"),o,u=!1,a=!1,f=!1,l,c;for(o in s)s[o].match(/(pre|left)/)?u=s[o]:s[o].match(/(post|right)/)&&(a=s[o]);u!==!1&&a!==!1&&(n===u?(c=e.one("#block-region-"+a),c&&(c.insert(i,"before"),f=!0)):(l=e.one("#block-region-"+u),l&&(l.insert(i,"after"),f=!0))),f===!1&&e.one("body").append(i),this.set("node",i)},change_block_move_icons:function(e){var t,n;this.get("node").all("."+r.BLOCK+" a."+r.EDITINGMOVE).each(function(i){i.ancestor("."+r.BLOCK).one("."+r.HEADER).setStyle("cursor","move"),t=e.get_drag_handle(i.getAttribute("title"),"","icon",!0),n=t.one("img"),n.addClass("iconsmall"),n.removeClass("icon"),i.replace(t)})},get_has_region_class:function(){return"has-region-"+this.get("region")},get_empty_region_class:function(){return"empty-region-"+this.get("region")},get_used_region_class:function(){return"used-region-"+this.get("region")},get_droptarget:function(){var e=this.get("node");return e.test('[data-droptarget="1"]')?e:e.one('[data-droptarget="1"]')},enable:function(){e.one("body").addClass(this.get_used_region_class()).removeClass(this.get_empty_region_class())},disable_if_required:function(){this.get("node").all("."+r.BLOCK).size()===0&&e.one("body").addClass(this.get_empty_region_class()).removeClass(this.get_used_region_class())}},e.extend(o,e.Base,o.prototype,{NAME:"core-blocks-dragdrop-blockregion",ATTRS:{manager:{writeOnce:"initOnly",validator:function(t){return e.Lang.isObject(t)&&t instanceof s}},region:{writeOnce:"initOnly",validator:function(t){return e.Lang.isString(t)}},node:{validator:function(t){return e.Lang.isObject(t)||e.Lang.isNull(t)}},hasblocks:{value:!1,validator:function(t){return e.Lang.isBoolean(t)}}}})},"@VERSION@",{requires:["base","node","io","dom","dd","dd-scroll","moodle-core-dragdrop","moodle-core-notification"]});
diff --git a/lib/yui/build/moodle-core-blocks/moodle-core-blocks.js b/lib/yui/build/moodle-core-blocks/moodle-core-blocks.js
index bc3bb526512..0d3802f534c 100644
--- a/lib/yui/build/moodle-core-blocks/moodle-core-blocks.js
+++ b/lib/yui/build/moodle-core-blocks/moodle-core-blocks.js
@@ -115,7 +115,7 @@ Y.extend(DRAGBLOCK, M.core.dragdrop, {
blocklist.each(function(blocknode) {
var move = blocknode.one('a.'+CSS.EDITINGMOVE);
if (move) {
- move.remove();
+ move.replace(this.get_drag_handle(move.getAttribute('title'), '', 'icon', true));
blocknode.one('.'+CSS.HEADER).setStyle('cursor', 'move');
}
}, this);
@@ -379,7 +379,8 @@ M.core.blockdraganddrop.init = function(params) {
M.core_blocks = M.core_blocks || {};
M.core_blocks.init_dragdrop = function(params) {
M.core.blockdraganddrop.init(params);
-};/**
+};
+/**
* This file contains the drag and drop manager class.
*
* Provides drag and drop functionality for blocks.
@@ -491,7 +492,7 @@ MANAGER.prototype = {
// This is VERY important as without it dnd won't work for empty block regions.
dragdelegation.on('drag:mouseDown', this.enable_all_regions, this);
- region.remove_block_move_icons();
+ region.change_block_move_icons(this);
}
},
@@ -769,7 +770,8 @@ Y.extend(MANAGER, M.core.dragdrop, MANAGER.prototype, {
value : []
}
}
-});/**
+});
+/**
* This file contains the Block Region class used by the drag and drop manager.
*
* Provides drag and drop functionality for blocks.
@@ -857,13 +859,19 @@ BLOCKREGION.prototype = {
},
/**
- * Removes the move icons and changes the cursor to a move icon when over the header.
- * @method remove_block_move_icons
+ * Change the move icons to enhanced drag handles and changes the cursor to a move icon when over the header.
+ * @param M.core.dragdrop the block manager
+ * @method change_block_move_icons
*/
- remove_block_move_icons : function() {
+ change_block_move_icons : function(manager) {
+ var handle, icon;
this.get('node').all('.'+CSS.BLOCK+' a.'+CSS.EDITINGMOVE).each(function(moveicon){
moveicon.ancestor('.'+CSS.BLOCK).one('.'+CSS.HEADER).setStyle('cursor', 'move');
- moveicon.remove();
+ handle = manager.get_drag_handle(moveicon.getAttribute('title'), '', 'icon', true);
+ icon = handle.one('img');
+ icon.addClass('iconsmall');
+ icon.removeClass('icon');
+ moveicon.replace(handle);
});
},
@@ -984,6 +992,7 @@ Y.extend(BLOCKREGION, Y.Base, BLOCKREGION.prototype, {
}
});
+
}, '@VERSION@', {
"requires": [
"base",
diff --git a/lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue-debug.js b/lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue-debug.js
index 13cf88e8a1c..60490eae120 100644
--- a/lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue-debug.js
+++ b/lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue-debug.js
@@ -128,6 +128,9 @@ Y.extend(DIALOGUE, Y.Panel, {
this.render();
this.show();
this.after('visibleChange', this.visibilityChanged, this);
+ if (config.center) {
+ this.centerDialogue();
+ }
if (!config.visible) {
this.hide();
}
diff --git a/lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue-min.js b/lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue-min.js
index 6e81e7e538e..17172da75f1 100644
--- a/lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue-min.js
+++ b/lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue-min.js
@@ -1 +1 @@
-YUI.add("moodle-core-notification-dialogue",function(e,t){var n,r,i,s,o,u,a,f;n="moodle-dialogue",r="notificationBase",i=0,s="yesLabel",o="noLabel",u="title",a="question",f={BASE:"moodle-dialogue-base",WRAP:"moodle-dialogue-wrap",HEADER:"moodle-dialogue-hd",BODY:"moodle-dialogue-bd",CONTENT:"moodle-dialogue-content",FOOTER:"moodle-dialogue-ft",HIDDEN:"hidden",LIGHTBOX:"moodle-dialogue-lightbox"},M.core=M.core||{};var l="Moodle dialogue",c,h,p,d,v;DIALOGUE_MODAL_CLASS="yui3-widget-modal",h=n+"-fullscreen",p=n+"-hidden",d="[role=dialog]",v="no-scrolling",c=function(t){i++;var n="moodle-dialogue-"+i;t.notificationBase=e.Node.create('
').append(e.Node.create('').append(e.Node.create('')).append(e.Node.create('')).append(e.Node.create(''))),e.one(document.body).append(t.notificationBase),t.additionalBaseClass&&t.notificationBase.addClass(t.additionalBaseClass),t.srcNode="#"+n,t.width=t.width||"400px",t.visible=t.visible||!1,t.center=t.centered||!0,t.centered=!1,t.COUNT=i,t.width==="auto"&&delete t.width,t.lightbox!==!1&&(t.modal=!0),delete t.lightbox,t.closeButton===!1?t.buttons=null:t.buttons=[{section:e.WidgetStdMod.HEADER,classNames:"closebutton",action:function(){this.hide()}}],c.superclass.constructor.apply(this,[t]),t.closeButton!==!1&&this.get("buttons").header[0].setAttribute("title",this.get("closeButtonTitle"))},e.extend(c,e.Panel,{_resizeevent:null,_orientationevent:null,initializer:function(t){var n;this.render(),this.show(),this.after("visibleChange",this.visibilityChanged,this),t.visible||this.hide(),this.set("COUNT",i),n=this.get("boundingBox"),t.extraClasses&&e.Array.each(t.extraClasses,n.addClass,n),t.visible&&this.applyZIndex()},applyZIndex:function(){var t=0,n,r;r=this.get("boundingBox"),this.get("zIndex")?r.setStyle("zIndex",this.get("zIndex")):(e.all(d).each(function(e){n=e.getStyle("zIndex"),n||(n=e.get("parentNode").getStyle("zIndex")),n&&(n=parseInt(n,10),n>t&&(t=n))}),t>0&&r.setStyle("zIndex",t+1))},toggleDocumentScrolling:function(){var t=e.one(e.config.doc.body),n=!0,r;r="."+h+", ."+DIALOGUE_MODAL_CLASS,e.all(r).each(function(e){e.hasClass(p)||(n=!1)}),e.UA.ie>0&&(t=e.one("html")),n?t.hasClass(v)&&t.removeClass(v):t.addClass(v)},visibilityChanged:function(t){var n;t.attrName==="visible"&&(this.get("maskNode").addClass(f.LIGHTBOX),t.prevVal&&!t.newVal&&(this._resizeevent&&(this._resizeevent.detach(),this._resizeevent=null),this._orientationevent&&(this._orientationevent.detach(),this._orientationevent=null)),!t.prevVal&&t.newVal&&(this.applyZIndex(),this.makeResponsive(),this.shouldResizeFullscreen()||this.get("draggable")&&(n="#"+this.get("id")+" ."+f.HEADER,this.plug(e.Plugin.Drag,{handles:[n]}),e.one(n).setStyle("cursor","move"))),this.get("center")&&!t.prevVal&&t.newVal&&this.centerDialogue(),this.toggleDocumentScrolling())},makeResponsive:function(){var t=this.get("boundingBox"),r;this.shouldResizeFullscreen()?(t.addClass(n+"-fullscreen"),t.setStyles({left:null,top:null,width:null,height:null}),r=e.one("#"+this.get("id")+" ."+f.BODY),r.setStyle("overflow","auto")):this.get("responsive")&&(t.removeClass(n+"-fullscreen").setStyles({overflow:"inherit",width:this.get("width"),height:this.get("height")}),r=e.one("#"+this.get("id")+" ."+f.BODY),r.setStyle("overflow","inherit"))},centerDialogue:function(){var t=this.get("boundingBox"),n=t.hasClass(p),r,i;if(this.shouldResizeFullscreen())return;n&&t.setStyle("top","-1000px").removeClass(p),r=Math.max(Math.round((t.get("winWidth")-t.get("offsetWidth"))/2),15),i=Math.max(Math.round((t.get("winHeight")-t.get("offsetHeight"))/2),15)+e.one(window).get("scrollTop"),t.setStyles({left:r,top:i}),n&&t.addClass(p)},shouldResizeFullscreen:function(){return window===window.parent&&this.get("responsive")&&Math.floor(e.one(document.body).get("winWidth"))').append(e.Node.create('').append(e.Node.create('')).append(e.Node.create('')).append(e.Node.create(''))),e.one(document.body).append(t.notificationBase),t.additionalBaseClass&&t.notificationBase.addClass(t.additionalBaseClass),t.srcNode="#"+n,t.width=t.width||"400px",t.visible=t.visible||!1,t.center=t.centered||!0,t.centered=!1,t.COUNT=i,t.width==="auto"&&delete t.width,t.lightbox!==!1&&(t.modal=!0),delete t.lightbox,t.closeButton===!1?t.buttons=null:t.buttons=[{section:e.WidgetStdMod.HEADER,classNames:"closebutton",action:function(){this.hide()}}],c.superclass.constructor.apply(this,[t]),t.closeButton!==!1&&this.get("buttons").header[0].setAttribute("title",this.get("closeButtonTitle"))},e.extend(c,e.Panel,{_resizeevent:null,_orientationevent:null,initializer:function(t){var n;this.render(),this.show(),this.after("visibleChange",this.visibilityChanged,this),t.center&&this.centerDialogue(),t.visible||this.hide(),this.set("COUNT",i),n=this.get("boundingBox"),t.extraClasses&&e.Array.each(t.extraClasses,n.addClass,n),t.visible&&this.applyZIndex()},applyZIndex:function(){var t=0,n,r;r=this.get("boundingBox"),this.get("zIndex")?r.setStyle("zIndex",this.get("zIndex")):(e.all(d).each(function(e){n=e.getStyle("zIndex"),n||(n=e.get("parentNode").getStyle("zIndex")),n&&(n=parseInt(n,10),n>t&&(t=n))}),t>0&&r.setStyle("zIndex",t+1))},toggleDocumentScrolling:function(){var t=e.one(e.config.doc.body),n=!0,r;r="."+h+", ."+DIALOGUE_MODAL_CLASS,e.all(r).each(function(e){e.hasClass(p)||(n=!1)}),e.UA.ie>0&&(t=e.one("html")),n?t.hasClass(v)&&t.removeClass(v):t.addClass(v)},visibilityChanged:function(t){var n;t.attrName==="visible"&&(this.get("maskNode").addClass(f.LIGHTBOX),t.prevVal&&!t.newVal&&(this._resizeevent&&(this._resizeevent.detach(),this._resizeevent=null),this._orientationevent&&(this._orientationevent.detach(),this._orientationevent=null)),!t.prevVal&&t.newVal&&(this.applyZIndex(),this.makeResponsive(),this.shouldResizeFullscreen()||this.get("draggable")&&(n="#"+this.get("id")+" ."+f.HEADER,this.plug(e.Plugin.Drag,{handles:[n]}),e.one(n).setStyle("cursor","move"))),this.get("center")&&!t.prevVal&&t.newVal&&this.centerDialogue(),this.toggleDocumentScrolling())},makeResponsive:function(){var t=this.get("boundingBox"),r;this.shouldResizeFullscreen()?(t.addClass(n+"-fullscreen"),t.setStyles({left:null,top:null,width:null,height:null}),r=e.one("#"+this.get("id")+" ."+f.BODY),r.setStyle("overflow","auto")):this.get("responsive")&&(t.removeClass(n+"-fullscreen").setStyles({overflow:"inherit",width:this.get("width"),height:this.get("height")}),r=e.one("#"+this.get("id")+" ."+f.BODY),r.setStyle("overflow","inherit"))},centerDialogue:function(){var t=this.get("boundingBox"),n=t.hasClass(p),r,i;if(this.shouldResizeFullscreen())return;n&&t.setStyle("top","-1000px").removeClass(p),r=Math.max(Math.round((t.get("winWidth")-t.get("offsetWidth"))/2),15),i=Math.max(Math.round((t.get("winHeight")-t.get("offsetHeight"))/2),15)+e.one(window).get("scrollTop"),t.setStyles({left:r,top:i}),n&&t.addClass(p)},shouldResizeFullscreen:function(){return window===window.parent&&this.get("responsive")&&Math.floor(e.one(document.body).get("winWidth"))')
.addClass(classname)
.setAttribute('title', title)
+ .setAttribute('tabIndex', 0)
+ .setAttribute('data-draggroups', this.groups);
dragelement.appendChild(dragicon);
+ dragelement.addClass(MOVEICON.cssclass);
+
return dragelement;
},
lock_drag_handle: function(drag, classname) {
- // Disable dragging
drag.removeHandle('.'+classname);
},
unlock_drag_handle: function(drag, classname) {
- // Enable dragging
drag.addHandle('.'+classname);
},
@@ -194,6 +199,268 @@ YUI.add('moodle-core-dragdrop', function(Y) {
this.drop_hit(e);
},
+ /**
+ * This is used to build the text for the heading of the keyboard
+ * drag drop menu and the text for the nodes in the list.
+ * @method find_element_text
+ * @param {Node} n The node to start searching for a valid text node.
+ * @returns {string} The text of the first text-like child node of n.
+ */
+ find_element_text : function(n) {
+ // The valid node types to get text from.
+ var nodes = n.all('h2, h3, h4, h5, span, p, div.no-overflow, div.dimmed_text');
+ var text = '';
+ debugger;
+
+ nodes.each(function () {
+ if (text == '') {
+ if (Y.Lang.trim(this.get('text')) != '') {
+ text = this.get('text');
+ }
+ }
+ });
+
+ if (text != '') {
+ return text;
+ }
+ return M.util.get_string('emptydragdropregion', 'moodle');
+ },
+
+ /**
+ * This is used to initiate a keyboard version of a drag and drop.
+ * A dialog will open listing all the valid drop targets that can be selected
+ * using tab, tab, tab, enter.
+ * @method global_start_keyboard_drag
+ * @param {Event} e The keydown / click event on the grab handle.
+ * @param {Node} dragcontainer The resolved draggable node (an ancestor of the drag handle).
+ * @param {Node} draghandle The node that triggered this action.
+ */
+ global_start_keyboard_drag : function(e, draghandle, dragcontainer) {
+ M.core.dragdrop.keydragcontainer = dragcontainer;
+ M.core.dragdrop.keydraghandle = draghandle;
+
+ // Indicate to a screenreader the node that is selected for drag and drop.
+ dragcontainer.setAttribute('aria-grabbed', 'true');
+ // Get the name of the thing to move.
+ var nodetitle = this.find_element_text(dragcontainer);
+ var dialogtitle = M.util.get_string('movecontent', 'moodle', nodetitle);
+
+ // Build the list of drop targets.
+ var droplist = Y.Node.create('
');
+ droplist.addClass('dragdrop-keyboard-drag');
+ var listitem;
+ var listitemtext;
+
+ // Search for possible drop targets.
+ var droptargets = Y.all('.' + this.samenodeclass + ', .' + this.parentnodeclass);
+
+ droptargets.each(function (node) {
+ var validdrop = false, labelroot = node;
+ if (node.drop && node.drop.inGroup(this.groups) && node.drop.get('node') != dragcontainer) {
+ // This is a drag and drop target with the same class as the grabbed node.
+ validdrop = true;
+ } else {
+ var elementgroups = node.getAttribute('data-draggroups').split(' ');
+ var i, j;
+ for (i = 0; i < elementgroups.length; i++) {
+ for (j = 0; j < this.groups.length; j++) {
+ if (elementgroups[i] == this.groups[j]) {
+ // This is a parent node of the grabbed node (used for dropping in empty sections).
+ validdrop = true;
+ // This node will have no text - so we get the first valid text from the parent.
+ labelroot = node.get('parentNode');
+ break;
+ }
+ }
+ if (validdrop) {
+ break;
+ }
+ }
+ }
+
+ if (validdrop) {
+ // It is a valid drop target - create a list item for it.
+ listitem = Y.Node.create('');
+ listlink = Y.Node.create('');
+ nodetitle = this.find_element_text(labelroot);
+
+ listitemtext = M.util.get_string('tocontent', 'moodle', nodetitle);
+ listlink.setContent(listitemtext);
+
+ // Add a data attribute so we can get the real drop target.
+ listlink.setAttribute('data-drop-target', node.get('id'));
+ // Notify the screen reader this is a valid drop target.
+ listlink.setAttribute('aria-dropeffect', 'move');
+ // Allow tabbing to the link.
+ listlink.setAttribute('tabindex', '0');
+
+ // Set the event listeners for enter, space or click.
+ listlink.on('click', this.global_keyboard_drop, this);
+ listlink.on('key', this.global_keyboard_drop, 'down:enter,32', this);
+
+ // Add to the list or drop targets.
+ listitem.append(listlink);
+ droplist.append(listitem);
+ }
+ }, this);
+
+ // Create the dialog for the interaction.
+ M.core.dragdrop.dropui = new M.core.dialogue({
+ headerContent : dialogtitle,
+ bodyContent : droplist,
+ draggable : true,
+ visible : true
+ });
+
+ // Focus the first drop target.
+ if (droplist.one('a')) {
+ droplist.one('a').focus();
+ }
+ },
+
+ /**
+ * This is used as a simulated drag/drop event in order to prevent any
+ * subtle bugs from creating a real instance of a drag drop event. This means
+ * there are no state changes in the Y.DD.DDM and any undefined functions
+ * will trigger an obvious and fatal error.
+ * The end result is that we call all our drag/drop handlers but do not bubble the
+ * event to anyone else.
+ *
+ * The functions/properties implemented in the wrapper are:
+ * e.target
+ * e.drag
+ * e.drop
+ * e.drag.get('node')
+ * e.drop.get('node')
+ * e.drag.addHandle()
+ * e.drag.removeHandle()
+ *
+ * @class simulated_drag_drop_event
+ * @param {Node} dragnode The drag container node
+ * @param {Node} dropnode The node to initiate the drop on
+ */
+ simulated_drag_drop_event : function(dragnode, dropnode) {
+
+ // Subclass for wrapping both drag and drop.
+ var dragdropwrapper = function(node) {
+ this.node = node;
+ }
+
+ // Method e.drag.get() - get the node.
+ dragdropwrapper.prototype.get = function(param) {
+ if (param == 'node' || param == 'dragNode' || param == 'dropNode') {
+ return this.node;
+ }
+ return null;
+ };
+
+ // Method e.drag.inGroup() - we have already run the group checks before triggering the event.
+ dragdropwrapper.prototype.inGroup = function() {
+ return true;
+ };
+
+ // Method e.drag.addHandle() - we don't want to run this.
+ dragdropwrapper.prototype.addHandle = function() {};
+ // Method e.drag.removeHandle() - we don't want to run this.
+ dragdropwrapper.prototype.removeHandle = function() {};
+
+ // Create instances of the dragdropwrapper.
+ this.drop = new dragdropwrapper(dropnode);
+ this.drag = new dragdropwrapper(dragnode);
+ this.target = this.drop;
+ },
+
+ /**
+ * This is used to complete a keyboard version of a drag and drop.
+ * A drop event will be simulated based on the drag and drop nodes.
+ * @method global_keyboard_drop
+ * @param {Event} e The keydown / click event on the proxy drop node.
+ */
+ global_keyboard_drop : function(e) {
+ // The drag node was saved.
+ var dragcontainer = M.core.dragdrop.keydragcontainer;
+ dragcontainer.setAttribute('aria-grabbed', 'false');
+ // The real drop node is stored in an attribute of the proxy.
+ var droptarget = Y.one('#' + e.target.getAttribute('data-drop-target'));
+
+ // Close the dialog.
+ M.core.dragdrop.dropui.hide();
+ // Cancel the event.
+ e.preventDefault();
+ // Convert to drag drop events.
+ var dragevent = new this.simulated_drag_drop_event(dragcontainer, dragcontainer);
+ var dropevent = new this.simulated_drag_drop_event(dragcontainer, droptarget);
+ // Simulate the full sequence.
+ this.drag_start(dragevent);
+ this.global_drop_over(dropevent);
+ this.global_drop_hit(dropevent);
+ M.core.dragdrop.keydraghandle.focus();
+ },
+
+ /**
+ * This is used to cancel a keyboard version of a drag and drop.
+ *
+ * @method global_cancel_keyboard_drag
+ */
+ global_cancel_keyboard_drag : function() {
+ if (M.core.dragdrop.keydragcontainer) {
+ M.core.dragdrop.keydragcontainer.setAttribute('aria-grabbed', 'false');
+ M.core.dragdrop.keydraghandle.focus();
+ M.core.dragdrop.keydragcontainer = null;
+ }
+ },
+
+ /**
+ * Process key events on the drag handles.
+ * @method global_keydown
+ * @param {Event} e The keydown / click event on the drag handle.
+ */
+ global_keydown : function(e) {
+ var draghandle = e.target,
+ dragcontainer,
+ draggroups;
+
+ if (e.keyCode == 27 ) {
+ // Escape to cancel from anywhere.
+ this.global_cancel_keyboard_drag();
+ e.preventDefault();
+ return;
+ }
+
+ // Only process events on a drag handle.
+ if (!draghandle.hasClass(MOVEICON.cssclass)) {
+ return;
+ }
+ // Do nothing if not space or enter.
+ if (e.keyCode != 13 && e.keyCode != 32) {
+ return;
+ }
+ // Check the drag groups to see if we are the handler for this node.
+ draggroups = e.target.getAttribute('data-draggroups').split(' ');
+ var i, j, validgroup = false;
+
+ for (i = 0; i < draggroups.length; i++) {
+ for (j = 0; j < this.groups.length; j++) {
+ if (draggroups[i] == this.groups[j]) {
+ validgroup = true;
+ break;
+ }
+ }
+ if (validgroup) {
+ break;
+ }
+ }
+ if (!validgroup) {
+ return;
+ }
+
+ // Valid event - start the keyboard drag.
+ dragcontainer = draghandle.ancestor('.yui3-dd-drop');
+ this.global_start_keyboard_drag(e, draghandle, dragcontainer);
+
+ e.preventDefault();
+ },
+
/*
* Abstract functions definitions
*/
@@ -211,4 +478,4 @@ YUI.add('moodle-core-dragdrop', function(Y) {
M.core = M.core || {};
M.core.dragdrop = DRAGDROP;
-}, '@VERSION@', {requires:['base', 'node', 'io', 'dom', 'dd', 'moodle-core-notification']});
+}, '@VERSION@', {requires:['base', 'node', 'io', 'dom', 'dd', 'event-key', 'event-focus', 'moodle-core-notification']});
diff --git a/lib/yui/src/blocks/js/blockregion.js b/lib/yui/src/blocks/js/blockregion.js
index 1d854b7d14a..4e131573156 100644
--- a/lib/yui/src/blocks/js/blockregion.js
+++ b/lib/yui/src/blocks/js/blockregion.js
@@ -88,13 +88,19 @@ BLOCKREGION.prototype = {
},
/**
- * Removes the move icons and changes the cursor to a move icon when over the header.
- * @method remove_block_move_icons
+ * Change the move icons to enhanced drag handles and changes the cursor to a move icon when over the header.
+ * @param M.core.dragdrop the block manager
+ * @method change_block_move_icons
*/
- remove_block_move_icons : function() {
+ change_block_move_icons : function(manager) {
+ var handle, icon;
this.get('node').all('.'+CSS.BLOCK+' a.'+CSS.EDITINGMOVE).each(function(moveicon){
moveicon.ancestor('.'+CSS.BLOCK).one('.'+CSS.HEADER).setStyle('cursor', 'move');
- moveicon.remove();
+ handle = manager.get_drag_handle(moveicon.getAttribute('title'), '', 'icon', true);
+ icon = handle.one('img');
+ icon.addClass('iconsmall');
+ icon.removeClass('icon');
+ moveicon.replace(handle);
});
},
@@ -213,4 +219,4 @@ Y.extend(BLOCKREGION, Y.Base, BLOCKREGION.prototype, {
}
}
}
-});
\ No newline at end of file
+});
diff --git a/lib/yui/src/blocks/js/blocks.js b/lib/yui/src/blocks/js/blocks.js
index 4597accb9eb..f3d17d225b5 100644
--- a/lib/yui/src/blocks/js/blocks.js
+++ b/lib/yui/src/blocks/js/blocks.js
@@ -113,7 +113,7 @@ Y.extend(DRAGBLOCK, M.core.dragdrop, {
blocklist.each(function(blocknode) {
var move = blocknode.one('a.'+CSS.EDITINGMOVE);
if (move) {
- move.remove();
+ move.replace(this.get_drag_handle(move.getAttribute('title'), '', 'icon', true));
blocknode.one('.'+CSS.HEADER).setStyle('cursor', 'move');
}
}, this);
@@ -377,4 +377,4 @@ M.core.blockdraganddrop.init = function(params) {
M.core_blocks = M.core_blocks || {};
M.core_blocks.init_dragdrop = function(params) {
M.core.blockdraganddrop.init(params);
-};
\ No newline at end of file
+};
diff --git a/lib/yui/src/blocks/js/manager.js b/lib/yui/src/blocks/js/manager.js
index 40490af9ab7..11d5e8795d7 100644
--- a/lib/yui/src/blocks/js/manager.js
+++ b/lib/yui/src/blocks/js/manager.js
@@ -111,7 +111,7 @@ MANAGER.prototype = {
// This is VERY important as without it dnd won't work for empty block regions.
dragdelegation.on('drag:mouseDown', this.enable_all_regions, this);
- region.remove_block_move_icons();
+ region.change_block_move_icons(this);
}
Y.log('Initialisation of drag and drop for blocks complete.', 'info');
},
@@ -390,4 +390,4 @@ Y.extend(MANAGER, M.core.dragdrop, MANAGER.prototype, {
value : []
}
}
-});
\ No newline at end of file
+});
diff --git a/lib/yui/src/notification/js/dialogue.js b/lib/yui/src/notification/js/dialogue.js
index cd489a1374a..28d4729f4ad 100644
--- a/lib/yui/src/notification/js/dialogue.js
+++ b/lib/yui/src/notification/js/dialogue.js
@@ -97,6 +97,9 @@ Y.extend(DIALOGUE, Y.Panel, {
this.render();
this.show();
this.after('visibleChange', this.visibilityChanged, this);
+ if (config.center) {
+ this.centerDialogue();
+ }
if (!config.visible) {
this.hide();
}
diff --git a/mod/assign/tests/generator/lib.php b/mod/assign/tests/generator/lib.php
index ffe9b6abbf9..126f43c478f 100644
--- a/mod/assign/tests/generator/lib.php
+++ b/mod/assign/tests/generator/lib.php
@@ -78,6 +78,7 @@ class mod_assign_generator extends testing_module_generator {
$record->coursemodule = $this->precreate_course_module($record->course, $options);
$id = assign_add_instance($record, null);
+ rebuild_course_cache($record->course, true);
return $this->post_add_instance($id, $record->coursemodule);
}
}
diff --git a/mod/assign/tests/lib_test.php b/mod/assign/tests/lib_test.php
index 420a22f17ea..304adc2f0dd 100644
--- a/mod/assign/tests/lib_test.php
+++ b/mod/assign/tests/lib_test.php
@@ -40,23 +40,26 @@ require_once($CFG->dirroot . '/mod/assign/tests/base_test.php');
class mod_assign_lib_testcase extends mod_assign_base_testcase {
public function test_assign_print_overview() {
+ global $DB;
$this->setUser($this->editingteachers[0]);
$this->create_instance();
$this->create_instance(array('duedate'=>time()));
+ $courses = $DB->get_records('course', array('id' => $this->course->id));
+
$this->setUser($this->students[0]);
$overview = array();
- assign_print_overview(array($this->course->id => $this->course), $overview);
+ assign_print_overview($courses, $overview);
$this->assertEquals(count($overview), 1);
$this->setUser($this->teachers[0]);
$overview = array();
- assign_print_overview(array($this->course->id => $this->course), $overview);
+ assign_print_overview($courses, $overview);
$this->assertEquals(count($overview), 1);
$this->setUser($this->editingteachers[0]);
$overview = array();
- assign_print_overview(array($this->course->id => $this->course), $overview);
+ assign_print_overview($courses, $overview);
$this->assertEquals(1, count($overview));
}
diff --git a/mod/chat/gui_ajax/index.php b/mod/chat/gui_ajax/index.php
index 4c2d69f0897..edd8e5a6534 100644
--- a/mod/chat/gui_ajax/index.php
+++ b/mod/chat/gui_ajax/index.php
@@ -79,7 +79,7 @@ echo $OUTPUT->box(html_writer::tag('h2', get_string('messages', 'chat'), array(
'
___time___
diff --git a/mod/chat/styles.css b/mod/chat/styles.css
index f4468c10bae..92c3d81e715 100644
--- a/mod/chat/styles.css
+++ b/mod/chat/styles.css
@@ -5,6 +5,15 @@
.path-mod-chat #messages-list,
.path-mod-chat #users-list {list-style-type:none;padding:0;margin:0}
.path-mod-chat #chat-header {overflow: hidden;}
+.path-mod-chat #chat-input-area table.generaltable td.cell {padding:1px;}
+
+/** shrink the text box so the theme link is always accessible */
+@media all and (max-device-width: 320px) {
+ .path-mod-chat #input-message {width: 150px;}
+}
+@media all and (min-device-width: 321px) and (max-device-width: 640px) {
+ .path-mod-chat #input-message {width: 175px;}
+}
/** styles for view.php **/
#page-mod-chat-view .chatcurrentusers .chatuserdetails {vertical-align: middle;}
@@ -24,8 +33,8 @@
/** YUI Overrides **/
.path-mod-chat .yui-layout-unit-top {background: #FFE39D;}
-.path-mod-chat .yui-layout-unit-right {border-top: 5px solid white;background: #FFD46B;}
-.path-mod-chat .yui-layout-unit-bottom {border-top: 5px solid white;background: #FFCB44;}
+.path-mod-chat .yui-layout-unit-right {background: #FFD46B;}
+.path-mod-chat .yui-layout-unit-bottom {background: #FFCB44;}
.path-mod-chat .yui-layout .yui-layout-hd {border:0;}
.path-mod-chat .yui-layout .yui-layout-unit div.yui-layout-bd {border:0;background: transparent;}
.path-mod-chat .yui-layout .yui-layout-unit div.yui-layout-unit-right {background: white;}
diff --git a/mod/forum/classes/post_form.php b/mod/forum/classes/post_form.php
new file mode 100644
index 00000000000..68404469835
--- /dev/null
+++ b/mod/forum/classes/post_form.php
@@ -0,0 +1,238 @@
+.
+
+/**
+ * File containing the form definition to post in the forum.
+ *
+ * @package mod_forum
+ * @copyright Jamie Pratt
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+require_once($CFG->libdir . '/formslib.php');
+require_once($CFG->dirroot . '/repository/lib.php');
+
+/**
+ * Class to post in a forum.
+ *
+ * @package mod_forum
+ * @copyright Jamie Pratt
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class mod_forum_post_form extends moodleform {
+
+ /**
+ * Returns the options array to use in filemanager for forum attachments
+ *
+ * @param stdClass $forum
+ * @return array
+ */
+ public static function attachment_options($forum) {
+ global $COURSE, $PAGE, $CFG;
+ $maxbytes = get_user_max_upload_file_size($PAGE->context, $CFG->maxbytes, $COURSE->maxbytes, $forum->maxbytes);
+ return array(
+ 'subdirs' => 0,
+ 'maxbytes' => $maxbytes,
+ 'maxfiles' => $forum->maxattachments,
+ 'accepted_types' => '*',
+ 'return_types' => FILE_INTERNAL
+ );
+ }
+
+ /**
+ * Returns the options array to use in forum text editor
+ *
+ * @return array
+ */
+ public static function editor_options() {
+ global $COURSE, $PAGE, $CFG;
+ // TODO: add max files and max size support
+ $maxbytes = get_user_max_upload_file_size($PAGE->context, $CFG->maxbytes, $COURSE->maxbytes);
+ return array(
+ 'maxfiles' => EDITOR_UNLIMITED_FILES,
+ 'maxbytes' => $maxbytes,
+ 'trusttext'=> true,
+ 'return_types'=> FILE_INTERNAL | FILE_EXTERNAL
+ );
+ }
+
+ /**
+ * Form definition
+ *
+ * @return void
+ */
+ function definition() {
+ global $CFG, $OUTPUT;
+
+ $mform =& $this->_form;
+
+ $course = $this->_customdata['course'];
+ $cm = $this->_customdata['cm'];
+ $coursecontext = $this->_customdata['coursecontext'];
+ $modcontext = $this->_customdata['modcontext'];
+ $forum = $this->_customdata['forum'];
+ $post = $this->_customdata['post'];
+ $edit = $this->_customdata['edit'];
+ $thresholdwarning = $this->_customdata['thresholdwarning'];
+
+ $mform->addElement('header', 'general', '');//fill in the data depending on page params later using set_data
+
+ // If there is a warning message and we are not editing a post we need to handle the warning.
+ if (!empty($thresholdwarning) && !$edit) {
+ // Here we want to display a warning if they can still post but have reached the warning threshold.
+ if ($thresholdwarning->canpost) {
+ $message = get_string($thresholdwarning->errorcode, $thresholdwarning->module, $thresholdwarning->additional);
+ $mform->addElement('html', $OUTPUT->notification($message));
+ }
+ }
+
+ $mform->addElement('text', 'subject', get_string('subject', 'forum'), 'size="48"');
+ $mform->setType('subject', PARAM_TEXT);
+ $mform->addRule('subject', get_string('required'), 'required', null, 'client');
+ $mform->addRule('subject', get_string('maximumchars', '', 255), 'maxlength', 255, 'client');
+
+ $mform->addElement('editor', 'message', get_string('message', 'forum'), null, self::editor_options());
+ $mform->setType('message', PARAM_RAW);
+ $mform->addRule('message', get_string('required'), 'required', null, 'client');
+
+ if (isset($forum->id) && forum_is_forcesubscribed($forum)) {
+
+ $mform->addElement('static', 'subscribemessage', get_string('subscription', 'forum'), get_string('everyoneissubscribed', 'forum'));
+ $mform->addElement('hidden', 'subscribe');
+ $mform->setType('subscribe', PARAM_INT);
+ $mform->addHelpButton('subscribemessage', 'subscription', 'forum');
+
+ } else if (isset($forum->forcesubscribe)&& $forum->forcesubscribe != FORUM_DISALLOWSUBSCRIBE ||
+ has_capability('moodle/course:manageactivities', $coursecontext)) {
+
+ $options = array();
+ $options[0] = get_string('subscribestop', 'forum');
+ $options[1] = get_string('subscribestart', 'forum');
+
+ $mform->addElement('select', 'subscribe', get_string('subscription', 'forum'), $options);
+ $mform->addHelpButton('subscribe', 'subscription', 'forum');
+ } else if ($forum->forcesubscribe == FORUM_DISALLOWSUBSCRIBE) {
+ $mform->addElement('static', 'subscribemessage', get_string('subscription', 'forum'), get_string('disallowsubscribe', 'forum'));
+ $mform->addElement('hidden', 'subscribe');
+ $mform->setType('subscribe', PARAM_INT);
+ $mform->addHelpButton('subscribemessage', 'subscription', 'forum');
+ }
+
+ if (!empty($forum->maxattachments) && $forum->maxbytes != 1 && has_capability('mod/forum:createattachment', $modcontext)) { // 1 = No attachments at all
+ $mform->addElement('filemanager', 'attachments', get_string('attachment', 'forum'), null, self::attachment_options($forum));
+ $mform->addHelpButton('attachments', 'attachment', 'forum');
+ }
+
+ if (empty($post->id) && has_capability('moodle/course:manageactivities', $coursecontext)) { // hack alert
+ $mform->addElement('checkbox', 'mailnow', get_string('mailnow', 'forum'));
+ }
+
+ if (!empty($CFG->forum_enabletimedposts) && !$post->parent && has_capability('mod/forum:viewhiddentimedposts', $coursecontext)) { // hack alert
+ $mform->addElement('header', 'displayperiod', get_string('displayperiod', 'forum'));
+
+ $mform->addElement('date_selector', 'timestart', get_string('displaystart', 'forum'), array('optional'=>true));
+ $mform->addHelpButton('timestart', 'displaystart', 'forum');
+
+ $mform->addElement('date_selector', 'timeend', get_string('displayend', 'forum'), array('optional'=>true));
+ $mform->addHelpButton('timeend', 'displayend', 'forum');
+
+ } else {
+ $mform->addElement('hidden', 'timestart');
+ $mform->setType('timestart', PARAM_INT);
+ $mform->addElement('hidden', 'timeend');
+ $mform->setType('timeend', PARAM_INT);
+ $mform->setConstants(array('timestart'=> 0, 'timeend'=>0));
+ }
+
+ if (groups_get_activity_groupmode($cm, $course)) { // hack alert
+ $groupdata = groups_get_activity_allowed_groups($cm);
+ $groupcount = count($groupdata);
+ $modulecontext = context_module::instance($cm->id);
+ $contextcheck = has_capability('mod/forum:movediscussions', $modulecontext) && empty($post->parent) && $groupcount > 1;
+ if ($contextcheck) {
+ $groupinfo = array('0' => get_string('allparticipants'));
+ foreach ($groupdata as $grouptemp) {
+ $groupinfo[$grouptemp->id] = $grouptemp->name;
+ }
+ $mform->addElement('select','groupinfo', get_string('group'), $groupinfo);
+ $mform->setDefault('groupinfo', $post->groupid);
+ } else {
+ if (empty($post->groupid)) {
+ $groupname = get_string('allparticipants');
+ } else {
+ $groupname = format_string($groupdata[$post->groupid]->name);
+ }
+ $mform->addElement('static', 'groupinfo', get_string('group'), $groupname);
+ }
+ }
+ //-------------------------------------------------------------------------------
+ // buttons
+ if (isset($post->edit)) { // hack alert
+ $submit_string = get_string('savechanges');
+ } else {
+ $submit_string = get_string('posttoforum', 'forum');
+ }
+ $this->add_action_buttons(false, $submit_string);
+
+ $mform->addElement('hidden', 'course');
+ $mform->setType('course', PARAM_INT);
+
+ $mform->addElement('hidden', 'forum');
+ $mform->setType('forum', PARAM_INT);
+
+ $mform->addElement('hidden', 'discussion');
+ $mform->setType('discussion', PARAM_INT);
+
+ $mform->addElement('hidden', 'parent');
+ $mform->setType('parent', PARAM_INT);
+
+ $mform->addElement('hidden', 'userid');
+ $mform->setType('userid', PARAM_INT);
+
+ $mform->addElement('hidden', 'groupid');
+ $mform->setType('groupid', PARAM_INT);
+
+ $mform->addElement('hidden', 'edit');
+ $mform->setType('edit', PARAM_INT);
+
+ $mform->addElement('hidden', 'reply');
+ $mform->setType('reply', PARAM_INT);
+ }
+
+ /**
+ * Form validation
+ *
+ * @param array $data data from the form.
+ * @param array $files files uploaded.
+ * @return array of errors.
+ */
+ function validation($data, $files) {
+ $errors = parent::validation($data, $files);
+ if (($data['timeend']!=0) && ($data['timestart']!=0) && $data['timeend'] <= $data['timestart']) {
+ $errors['timeend'] = get_string('timestartenderror', 'forum');
+ }
+ if (empty($data['message']['text'])) {
+ $errors['message'] = get_string('erroremptymessage', 'forum');
+ }
+ if (empty($data['subject'])) {
+ $errors['subject'] = get_string('erroremptysubject', 'forum');
+ }
+ return $errors;
+ }
+}
+
diff --git a/mod/forum/lib.php b/mod/forum/lib.php
index 8a5a46b449d..917bb8ab03c 100644
--- a/mod/forum/lib.php
+++ b/mod/forum/lib.php
@@ -27,7 +27,6 @@ defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir.'/filelib.php');
require_once($CFG->libdir.'/eventslib.php');
require_once($CFG->dirroot.'/user/selector/lib.php');
-require_once($CFG->dirroot.'/mod/forum/post_form.php');
/// CONSTANTS ///////////////////////////////////////////////////////////
@@ -8105,16 +8104,18 @@ function forum_get_courses_user_posted_in($user, $discussionsonly = false, $incl
// table and join to the userid there. If we are looking for posts then we need
// to join to the forum_posts table.
if (!$discussionsonly) {
- $joinsql = 'JOIN {forum_discussions} fd ON fd.course = c.id
- JOIN {forum_posts} fp ON fp.discussion = fd.id';
- $wheresql = 'fp.userid = :userid';
- $params = array('userid' => $user->id);
+ $subquery = "(SELECT DISTINCT fd.course
+ FROM {forum_discussions} fd
+ JOIN {forum_posts} fp ON fp.discussion = fd.id
+ WHERE fp.userid = :userid )";
} else {
- $joinsql = 'JOIN {forum_discussions} fd ON fd.course = c.id';
- $wheresql = 'fd.userid = :userid';
- $params = array('userid' => $user->id);
+ $subquery= "(SELECT DISTINCT fd.course
+ FROM {forum_discussions} fd
+ WHERE fd.userid = :userid )";
}
+ $params = array('userid' => $user->id);
+
// Join to the context table so that we can preload contexts if required.
if ($includecontexts) {
$ctxselect = ', ' . context_helper::get_preload_record_columns_sql('ctx');
@@ -8127,14 +8128,13 @@ function forum_get_courses_user_posted_in($user, $discussionsonly = false, $incl
// Now we need to get all of the courses to search.
// All courses where the user has posted within a forum will be returned.
- $sql = "SELECT DISTINCT c.* $ctxselect
+ $sql = "SELECT c.* $ctxselect
FROM {course} c
- $joinsql
$ctxjoin
- WHERE $wheresql";
+ WHERE c.id IN ($subquery)";
$courses = $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
if ($includecontexts) {
- array_map('context_instance_preload', $courses);
+ array_map('context_helper::preload_from_record', $courses);
}
return $courses;
}
diff --git a/mod/forum/post.php b/mod/forum/post.php
index b938dbbe7a1..3a7bf1bc957 100644
--- a/mod/forum/post.php
+++ b/mod/forum/post.php
@@ -505,8 +505,6 @@ if (!isset($forum->maxattachments)) { // TODO - delete this once we add a field
$forum->maxattachments = 3;
}
-require_once('post_form.php');
-
$thresholdwarning = forum_check_throttling($forum, $cm);
$mform_post = new mod_forum_post_form('post.php', array('course' => $course,
'cm' => $cm,
diff --git a/mod/forum/post_form.php b/mod/forum/post_form.php
index 1cb99ec37b8..76ecf65c2f1 100644
--- a/mod/forum/post_form.php
+++ b/mod/forum/post_form.php
@@ -16,204 +16,13 @@
// along with Moodle. If not, see .
/**
- * @package mod-forum
- * @copyright Jamie Pratt
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @package mod_forum
+ * @copyright Jamie Pratt
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @deprecated since 2.6
*/
-if (!defined('MOODLE_INTERNAL')) {
- die('Direct access to this script is forbidden.'); /// It must be included from a Moodle page
-}
-
-require_once($CFG->libdir.'/formslib.php');
-
-class mod_forum_post_form extends moodleform {
-
- /**
- * Returns the options array to use in filemanager for forum attachments
- *
- * @param stdClass $forum
- * @return array
- */
- public static function attachment_options($forum) {
- global $COURSE, $PAGE, $CFG;
- $maxbytes = get_user_max_upload_file_size($PAGE->context, $CFG->maxbytes, $COURSE->maxbytes, $forum->maxbytes);
- return array(
- 'subdirs' => 0,
- 'maxbytes' => $maxbytes,
- 'maxfiles' => $forum->maxattachments,
- 'accepted_types' => '*',
- 'return_types' => FILE_INTERNAL
- );
- }
-
- /**
- * Returns the options array to use in forum text editor
- *
- * @return array
- */
- public static function editor_options() {
- global $COURSE, $PAGE, $CFG;
- // TODO: add max files and max size support
- $maxbytes = get_user_max_upload_file_size($PAGE->context, $CFG->maxbytes, $COURSE->maxbytes);
- return array(
- 'maxfiles' => EDITOR_UNLIMITED_FILES,
- 'maxbytes' => $maxbytes,
- 'trusttext'=> true,
- 'return_types'=> FILE_INTERNAL | FILE_EXTERNAL
- );
- }
-
- function definition() {
- global $CFG, $OUTPUT;
-
- $mform =& $this->_form;
-
- $course = $this->_customdata['course'];
- $cm = $this->_customdata['cm'];
- $coursecontext = $this->_customdata['coursecontext'];
- $modcontext = $this->_customdata['modcontext'];
- $forum = $this->_customdata['forum'];
- $post = $this->_customdata['post'];
- $edit = $this->_customdata['edit'];
- $thresholdwarning = $this->_customdata['thresholdwarning'];
-
- $mform->addElement('header', 'general', '');//fill in the data depending on page params later using set_data
-
- // If there is a warning message and we are not editing a post we need to handle the warning.
- if (!empty($thresholdwarning) && !$edit) {
- // Here we want to display a warning if they can still post but have reached the warning threshold.
- if ($thresholdwarning->canpost) {
- $message = get_string($thresholdwarning->errorcode, $thresholdwarning->module, $thresholdwarning->additional);
- $mform->addElement('html', $OUTPUT->notification($message));
- }
- }
-
- $mform->addElement('text', 'subject', get_string('subject', 'forum'), 'size="48"');
- $mform->setType('subject', PARAM_TEXT);
- $mform->addRule('subject', get_string('required'), 'required', null, 'client');
- $mform->addRule('subject', get_string('maximumchars', '', 255), 'maxlength', 255, 'client');
-
- $mform->addElement('editor', 'message', get_string('message', 'forum'), null, self::editor_options());
- $mform->setType('message', PARAM_RAW);
- $mform->addRule('message', get_string('required'), 'required', null, 'client');
-
- if (isset($forum->id) && forum_is_forcesubscribed($forum)) {
-
- $mform->addElement('static', 'subscribemessage', get_string('subscription', 'forum'), get_string('everyoneissubscribed', 'forum'));
- $mform->addElement('hidden', 'subscribe');
- $mform->setType('subscribe', PARAM_INT);
- $mform->addHelpButton('subscribemessage', 'subscription', 'forum');
-
- } else if (isset($forum->forcesubscribe)&& $forum->forcesubscribe != FORUM_DISALLOWSUBSCRIBE ||
- has_capability('moodle/course:manageactivities', $coursecontext)) {
-
- $options = array();
- $options[0] = get_string('subscribestop', 'forum');
- $options[1] = get_string('subscribestart', 'forum');
-
- $mform->addElement('select', 'subscribe', get_string('subscription', 'forum'), $options);
- $mform->addHelpButton('subscribe', 'subscription', 'forum');
- } else if ($forum->forcesubscribe == FORUM_DISALLOWSUBSCRIBE) {
- $mform->addElement('static', 'subscribemessage', get_string('subscription', 'forum'), get_string('disallowsubscribe', 'forum'));
- $mform->addElement('hidden', 'subscribe');
- $mform->setType('subscribe', PARAM_INT);
- $mform->addHelpButton('subscribemessage', 'subscription', 'forum');
- }
-
- if (!empty($forum->maxattachments) && $forum->maxbytes != 1 && has_capability('mod/forum:createattachment', $modcontext)) { // 1 = No attachments at all
- $mform->addElement('filemanager', 'attachments', get_string('attachment', 'forum'), null, self::attachment_options($forum));
- $mform->addHelpButton('attachments', 'attachment', 'forum');
- }
-
- if (empty($post->id) && has_capability('moodle/course:manageactivities', $coursecontext)) { // hack alert
- $mform->addElement('checkbox', 'mailnow', get_string('mailnow', 'forum'));
- }
-
- if (!empty($CFG->forum_enabletimedposts) && !$post->parent && has_capability('mod/forum:viewhiddentimedposts', $coursecontext)) { // hack alert
- $mform->addElement('header', 'displayperiod', get_string('displayperiod', 'forum'));
-
- $mform->addElement('date_selector', 'timestart', get_string('displaystart', 'forum'), array('optional'=>true));
- $mform->addHelpButton('timestart', 'displaystart', 'forum');
-
- $mform->addElement('date_selector', 'timeend', get_string('displayend', 'forum'), array('optional'=>true));
- $mform->addHelpButton('timeend', 'displayend', 'forum');
-
- } else {
- $mform->addElement('hidden', 'timestart');
- $mform->setType('timestart', PARAM_INT);
- $mform->addElement('hidden', 'timeend');
- $mform->setType('timeend', PARAM_INT);
- $mform->setConstants(array('timestart'=> 0, 'timeend'=>0));
- }
-
- if (groups_get_activity_groupmode($cm, $course)) { // hack alert
- $groupdata = groups_get_activity_allowed_groups($cm);
- $groupcount = count($groupdata);
- $modulecontext = context_module::instance($cm->id);
- $contextcheck = has_capability('mod/forum:movediscussions', $modulecontext) && empty($post->parent) && $groupcount > 1;
- if ($contextcheck) {
- $groupinfo = array('0' => get_string('allparticipants'));
- foreach ($groupdata as $grouptemp) {
- $groupinfo[$grouptemp->id] = $grouptemp->name;
- }
- $mform->addElement('select','groupinfo', get_string('group'), $groupinfo);
- $mform->setDefault('groupinfo', $post->groupid);
- } else {
- if (empty($post->groupid)) {
- $groupname = get_string('allparticipants');
- } else {
- $groupname = format_string($groupdata[$post->groupid]->name);
- }
- $mform->addElement('static', 'groupinfo', get_string('group'), $groupname);
- }
- }
- //-------------------------------------------------------------------------------
- // buttons
- if (isset($post->edit)) { // hack alert
- $submit_string = get_string('savechanges');
- } else {
- $submit_string = get_string('posttoforum', 'forum');
- }
- $this->add_action_buttons(false, $submit_string);
-
- $mform->addElement('hidden', 'course');
- $mform->setType('course', PARAM_INT);
-
- $mform->addElement('hidden', 'forum');
- $mform->setType('forum', PARAM_INT);
-
- $mform->addElement('hidden', 'discussion');
- $mform->setType('discussion', PARAM_INT);
-
- $mform->addElement('hidden', 'parent');
- $mform->setType('parent', PARAM_INT);
-
- $mform->addElement('hidden', 'userid');
- $mform->setType('userid', PARAM_INT);
-
- $mform->addElement('hidden', 'groupid');
- $mform->setType('groupid', PARAM_INT);
-
- $mform->addElement('hidden', 'edit');
- $mform->setType('edit', PARAM_INT);
-
- $mform->addElement('hidden', 'reply');
- $mform->setType('reply', PARAM_INT);
- }
-
- function validation($data, $files) {
- $errors = parent::validation($data, $files);
- if (($data['timeend']!=0) && ($data['timestart']!=0) && $data['timeend'] <= $data['timestart']) {
- $errors['timeend'] = get_string('timestartenderror', 'forum');
- }
- if (empty($data['message']['text'])) {
- $errors['message'] = get_string('erroremptymessage', 'forum');
- }
- if (empty($data['subject'])) {
- $errors['subject'] = get_string('erroremptysubject', 'forum');
- }
- return $errors;
- }
-}
+defined('MOODLE_INTERNAL') || die();
+// TODO MDL-41313 Remove this file.
+debugging('Do not include post_form.php directly, it is now using automatic class loading.', DEBUG_DEVELOPER);
diff --git a/mod/forum/tests/lib_test.php b/mod/forum/tests/lib_test.php
index 78450913092..90de8753058 100644
--- a/mod/forum/tests/lib_test.php
+++ b/mod/forum/tests/lib_test.php
@@ -75,4 +75,97 @@ class mod_forum_lib_testcase extends advanced_testcase {
$this->assertEventLegacyData($expected, $event);
}
+ public function test_forum_get_courses_user_posted_in() {
+ $this->resetAfterTest();
+
+ $user1 = $this->getDataGenerator()->create_user();
+ $user2 = $this->getDataGenerator()->create_user();
+ $user3 = $this->getDataGenerator()->create_user();
+
+ $course1 = $this->getDataGenerator()->create_course();
+ $course2 = $this->getDataGenerator()->create_course();
+ $course3 = $this->getDataGenerator()->create_course();
+
+ // Create 3 forums, one in each course.
+ $record = new stdClass();
+ $record->course = $course1->id;
+ $forum1 = $this->getDataGenerator()->create_module('forum', $record);
+
+ $record = new stdClass();
+ $record->course = $course2->id;
+ $forum2 = $this->getDataGenerator()->create_module('forum', $record);
+
+ $record = new stdClass();
+ $record->course = $course3->id;
+ $forum3 = $this->getDataGenerator()->create_module('forum', $record);
+
+ // Add a second forum in course 1.
+ $record = new stdClass();
+ $record->course = $course1->id;
+ $forum4 = $this->getDataGenerator()->create_module('forum', $record);
+
+ // Add discussions to course 1 started by user1.
+ $record = new stdClass();
+ $record->course = $course1->id;
+ $record->userid = $user1->id;
+ $record->forum = $forum1->id;
+ $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
+
+ $record = new stdClass();
+ $record->course = $course1->id;
+ $record->userid = $user1->id;
+ $record->forum = $forum4->id;
+ $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
+
+ // Add discussions to course2 started by user1.
+ $record = new stdClass();
+ $record->course = $course2->id;
+ $record->userid = $user1->id;
+ $record->forum = $forum2->id;
+ $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
+
+ // Add discussions to course 3 started by user2.
+ $record = new stdClass();
+ $record->course = $course3->id;
+ $record->userid = $user2->id;
+ $record->forum = $forum3->id;
+ $discussion3 = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
+
+ // Add post to course 3 by user1.
+ $record = new stdClass();
+ $record->course = $course3->id;
+ $record->userid = $user1->id;
+ $record->forum = $forum3->id;
+ $record->discussion = $discussion3->id;
+ $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
+
+ // User 3 hasn't posted anything, so shouldn't get any results.
+ $user3courses = forum_get_courses_user_posted_in($user3);
+ $this->assertEmpty($user3courses);
+
+ // User 2 has only posted in course3.
+ $user2courses = forum_get_courses_user_posted_in($user2);
+ $this->assertCount(1, $user2courses);
+ $user2course = array_shift($user2courses);
+ $this->assertEquals($course3->id, $user2course->id);
+ $this->assertEquals($course3->shortname, $user2course->shortname);
+
+ // User 1 has posted in all 3 courses.
+ $user1courses = forum_get_courses_user_posted_in($user1);
+ $this->assertCount(3, $user1courses);
+ foreach ($user1courses as $course) {
+ $this->assertContains($course->id, array($course1->id, $course2->id, $course3->id));
+ $this->assertContains($course->shortname, array($course1->shortname, $course2->shortname,
+ $course3->shortname));
+
+ }
+
+ // User 1 has only started a discussion in course 1 and 2 though.
+ $user1courses = forum_get_courses_user_posted_in($user1, true);
+ $this->assertCount(2, $user1courses);
+ foreach ($user1courses as $course) {
+ $this->assertContains($course->id, array($course1->id, $course2->id));
+ $this->assertContains($course->shortname, array($course1->shortname, $course2->shortname));
+ }
+ }
}
diff --git a/mod/forum/upgrade.txt b/mod/forum/upgrade.txt
index ef5c0798820..6ce0fef6dae 100644
--- a/mod/forum/upgrade.txt
+++ b/mod/forum/upgrade.txt
@@ -1,6 +1,11 @@
This files describes API changes in /mod/forum/*,
information provided here is intended especially for developers.
+=== 2.6 ===
+
+* The file post_form.php should not be included, the class it contained has
+ been moved so that it can benefit from autoloading.
+
=== 2.5 ===
The function forum_check_throttling has been changed so that a warning object is returned when a user has reached the 'Post threshold for warning' or
@@ -11,4 +16,4 @@ as a HTML element, where it is more noticeable. False is returned if there is no
* mod/forum:allowforcesubscribe capability will be forcefully assigned to frontpage role, as it was mistakenly missed off
when the capability was initially created. If you don't want users with frontpage role to get forum (with forcesubscribe) emails,
-then please remove this capability for frontpage role.
\ No newline at end of file
+then please remove this capability for frontpage role.
diff --git a/mod/quiz/renderer.php b/mod/quiz/renderer.php
index 4d7cc9b8a85..41e109c7595 100644
--- a/mod/quiz/renderer.php
+++ b/mod/quiz/renderer.php
@@ -1083,14 +1083,13 @@ class mod_quiz_renderer extends plugin_renderer_base {
}
if ($viewobj->gradebookfeedback) {
$resultinfo .= $this->heading(get_string('comment', 'quiz'), 3, 'main');
- $resultinfo .= '
'.$viewobj->gradebookfeedback.
- "
\n";
+ $resultinfo .= html_writer::div($viewobj->gradebookfeedback, 'quizteacherfeedback') . "\n";
}
if ($viewobj->feedbackcolumn) {
$resultinfo .= $this->heading(get_string('overallfeedback', 'quiz'), 3, 'main');
- $resultinfo .= html_writer::tag('p',
+ $resultinfo .= html_writer::div(
quiz_feedback_for_grade($viewobj->mygrade, $quiz, $context),
- array('class' => 'quizgradefeedback'))."\n";
+ 'quizgradefeedback') . "\n";
}
if ($resultinfo) {
diff --git a/mod/scorm/datamodels/aicclib.php b/mod/scorm/datamodels/aicclib.php
index 3aecc7f00e5..c33fd3f3796 100644
--- a/mod/scorm/datamodels/aicclib.php
+++ b/mod/scorm/datamodels/aicclib.php
@@ -134,11 +134,17 @@ function scorm_parse_aicc($scorm) {
$extension = strtolower(substr($ext, 1));
if (in_array($extension, $extaiccfiles)) {
$id = strtolower(basename($filename, $ext));
+ if (!isset($ids[$id])) {
+ $ids[$id] = new stdClass();
+ }
$ids[$id]->$extension = $file;
}
}
foreach ($ids as $courseid => $id) {
+ if (!isset($courses[$courseid])) {
+ $courses[$courseid] = new stdClass();
+ }
if (isset($id->crs)) {
$contents = $id->crs->get_content();
$rows = explode("\r\n", $contents);
@@ -169,6 +175,9 @@ function scorm_parse_aicc($scorm) {
if (preg_match($regexp, $rows[$i], $matches)) {
for ($j=0; $jcolumns); $j++) {
$column = $columns->columns[$j];
+ if (!isset($courses[$courseid]->elements[substr(trim($matches[$columns->mastercol+1]), 1 , -1)])) {
+ $courses[$courseid]->elements[substr(trim($matches[$columns->mastercol+1]), 1 , -1)] = new stdClass();
+ }
$courses[$courseid]->elements[substr(trim($matches[$columns->mastercol+1]), 1 , -1)]->$column = substr(trim($matches[$j+1]), 1, -1);
}
}
@@ -268,13 +277,16 @@ function scorm_parse_aicc($scorm) {
if (isset($course->elements)) {
foreach ($course->elements as $element) {
unset($sco);
+ $sco = new stdClass();
$sco->identifier = $element->system_id;
$sco->scorm = $scorm->id;
$sco->organization = $course->id;
$sco->title = $element->title;
- if (!isset($element->parent) || strtolower($element->parent) == 'root') {
+ if (!isset($element->parent)) {
$sco->parent = '/';
+ } else if (strtolower($element->parent) == 'root') {
+ $sco->parent = $course->id;
} else {
$sco->parent = $element->parent;
}
diff --git a/mod/scorm/db/upgrade.php b/mod/scorm/db/upgrade.php
index 13646edb426..d6c1cff6afb 100644
--- a/mod/scorm/db/upgrade.php
+++ b/mod/scorm/db/upgrade.php
@@ -77,12 +77,15 @@ function xmldb_scorm_upgrade($oldversion) {
// Moodle v2.4.0 release upgrade line
- // Put any upgrade step following this
+ // Put any upgrade step following this.
+
+ // Moodle v2.5.0 release upgrade line.
+ // Put any upgrade step following this.
// Remove old imsrepository type - convert any existing records to external type to help prevent major errors.
- if ($oldversion < 2013050101) {
+ if ($oldversion < 2013081301) {
$scorms = $DB->get_recordset('scorm', array('scormtype' => 'imsrepository'));
- foreach($scorms as $scorm) {
+ foreach ($scorms as $scorm) {
$scorm->scormtype = SCORM_TYPE_EXTERNAL;
if (!empty($CFG->repository)) { // Fix path to imsmanifest if $CFG->repository is set.
$scorm->reference = $CFG->repository.substr($scorm->reference, 1).'/imsmanifest.xml';
@@ -91,13 +94,25 @@ function xmldb_scorm_upgrade($oldversion) {
$scorm->revision++;
$DB->update_record('scorm', $scorm);
}
- upgrade_mod_savepoint(true, 2013050101, 'scorm');
+ upgrade_mod_savepoint(true, 2013081301, 'scorm');
}
-
- // Moodle v2.5.0 release upgrade line.
- // Put any upgrade step following this.
-
+ // Fix AICC parent/child relationships (MDL-37394).
+ if ($oldversion < 2013081302) {
+ // Get all AICC packages.
+ $aiccpackages = $DB->get_recordset('scorm', array('version' => 'AICC'), '', 'id');
+ foreach ($aiccpackages as $aicc) {
+ $sql = "UPDATE {scorm_scoes}
+ SET parent = organization
+ WHERE scorm = ?
+ AND " . $DB->sql_isempty('scorm_scoes', 'manifest', false, false) . "
+ AND " . $DB->sql_isnotempty('scorm_scoes', 'organization', false, false) . "
+ AND parent = '/'";
+ $DB->execute($sql, array($aicc->id));
+ }
+ $aiccpackages->close();
+ upgrade_mod_savepoint(true, 2013081302, 'scorm');
+ }
return true;
}
diff --git a/mod/scorm/version.php b/mod/scorm/version.php
index ff309ccdeba..9f2d6c1b63d 100644
--- a/mod/scorm/version.php
+++ b/mod/scorm/version.php
@@ -25,7 +25,7 @@
defined('MOODLE_INTERNAL') || die();
-$module->version = 2013050101; // The current module version (Date: YYYYMMDDXX)
+$module->version = 2013081302; // The current module version (Date: YYYYMMDDXX)
$module->requires = 2013050100; // Requires this Moodle version
$module->component = 'mod_scorm'; // Full name of the plugin (used for diagnostics)
$module->cron = 300;
diff --git a/mod/workshop/renderer.php b/mod/workshop/renderer.php
index a4311a8a597..4a29a74b9f8 100644
--- a/mod/workshop/renderer.php
+++ b/mod/workshop/renderer.php
@@ -339,6 +339,7 @@ class mod_workshop_renderer extends plugin_renderer_base {
* @return string HTML to be echoed
*/
protected function render_workshop_allocation_result(workshop_allocation_result $result) {
+ global $CFG;
$status = $result->get_status();
@@ -384,7 +385,7 @@ class mod_workshop_renderer extends plugin_renderer_base {
if (is_array($logs) and !empty($logs)) {
$o .= html_writer::start_tag('ul', array('class' => 'allocation-init-results'));
foreach ($logs as $log) {
- if ($log->type == 'debug' and !debugging('', DEBUG_DEVELOPER)) {
+ if ($log->type == 'debug' and !$CFG->debugdeveloper) {
// display allocation debugging messages for developers only
continue;
}
diff --git a/question/behaviour/manualgraded/tests/walkthrough_test.php b/question/behaviour/manualgraded/tests/walkthrough_test.php
index 89168050290..b8f2c79671c 100644
--- a/question/behaviour/manualgraded/tests/walkthrough_test.php
+++ b/question/behaviour/manualgraded/tests/walkthrough_test.php
@@ -38,7 +38,7 @@ require_once(dirname(__FILE__) . '/../../../engine/tests/helpers.php');
* @copyright 2009 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-class qbehaviour_manualgraded_walkthrough_test extends qbehaviour_walkthrough_test_base {
+class qbehaviour_manualgraded_walkthrough_testcase extends qbehaviour_walkthrough_test_base {
public function test_manual_graded_essay() {
// Create an essay question.
@@ -56,7 +56,7 @@ class qbehaviour_manualgraded_walkthrough_test extends qbehaviour_walkthrough_te
$this->get_does_not_contain_feedback_expectation());
// Simulate some data submitted by the student.
- $this->process_submission(array('answer' => 'This is my wonderful essay!', 'answerformat' => FORMAT_PLAIN));
+ $this->process_submission(array('answer' => 'This is my wonderful essay!', 'answerformat' => FORMAT_HTML));
// Verify.
$this->check_current_state(question_state::$complete);
@@ -68,16 +68,16 @@ class qbehaviour_manualgraded_walkthrough_test extends qbehaviour_walkthrough_te
// Process the same data again, check it does not create a new step.
$numsteps = $this->get_step_count();
- $this->process_submission(array('answer' => 'This is my wonderful essay!', 'answerformat' => FORMAT_PLAIN));
+ $this->process_submission(array('answer' => 'This is my wonderful essay!', 'answerformat' => FORMAT_HTML));
$this->check_step_count($numsteps);
// Process different data, check it creates a new step.
- $this->process_submission(array('answer' => ''));
+ $this->process_submission(array('answer' => '', 'answerformat' => FORMAT_HTML));
$this->check_step_count($numsteps + 1);
$this->check_current_state(question_state::$todo);
// Change back, check it creates a new step.
- $this->process_submission(array('answer' => 'This is my wonderful essay!', 'answerformat' => FORMAT_PLAIN));
+ $this->process_submission(array('answer' => 'This is my wonderful essay!', 'answerformat' => FORMAT_HTML));
$this->check_step_count($numsteps + 2);
// Finish the attempt.
@@ -206,7 +206,7 @@ class qbehaviour_manualgraded_walkthrough_test extends qbehaviour_walkthrough_te
$this->check_current_mark(null);
// Simulate some data submitted by the student.
- $this->process_submission(array('answer' => 'This is my wonderful essay!', 'answerformat' => FORMAT_PLAIN));
+ $this->process_submission(array('answer' => 'This is my wonderful essay!', 'answerformat' => FORMAT_HTML));
// Verify.
$this->check_current_state(question_state::$complete);
@@ -283,7 +283,7 @@ class qbehaviour_manualgraded_walkthrough_test extends qbehaviour_walkthrough_te
$this->get_does_not_contain_feedback_expectation());
// Simulate some data submitted by the student.
- $this->process_submission(array('answer' => 'This is my wonderful essay!', 'answerformat' => FORMAT_PLAIN));
+ $this->process_submission(array('answer' => 'This is my wonderful essay!', 'answerformat' => FORMAT_HTML));
// Verify.
$this->check_current_state(question_state::$complete);
diff --git a/question/category.php b/question/category.php
index ed6a085607d..0d6288710e8 100644
--- a/question/category.php
+++ b/question/category.php
@@ -104,10 +104,14 @@ if ($param->delete && ($questionstomove = $DB->count_records("question", array("
if ($qcobject->catform->is_cancelled()) {
redirect($thispageurl);
} else if ($catformdata = $qcobject->catform->get_data()) {
+ $catformdata->infoformat = $catformdata->info['format'];
+ $catformdata->info = $catformdata->info['text'];
if (!$catformdata->id) {//new category
- $qcobject->add_category($catformdata->parent, $catformdata->name, $catformdata->info);
+ $qcobject->add_category($catformdata->parent, $catformdata->name,
+ $catformdata->info, false, $catformdata->infoformat);
} else {
- $qcobject->update_category($catformdata->id, $catformdata->parent, $catformdata->name, $catformdata->info);
+ $qcobject->update_category($catformdata->id, $catformdata->parent,
+ $catformdata->name, $catformdata->info, $catformdata->infoformat);
}
redirect($thispageurl);
} else if ((!empty($param->delete) and (!$questionstomove) and confirm_sesskey())) {
diff --git a/question/category_class.php b/question/category_class.php
index f6ef2bc7a41..39740fde6e7 100644
--- a/question/category_class.php
+++ b/question/category_class.php
@@ -134,25 +134,28 @@ class question_category_list_item extends list_item {
*/
class question_category_object {
- var $str;
/**
- * Nested lists to display categories.
- *
- * @var array
+ * @var array common language strings.
*/
- var $editlists = array();
- var $newtable;
- var $tab;
- var $tabsize = 3;
+ public $str;
+
+ /**
+ * @var array nested lists to display categories.
+ */
+ public $editlists = array();
+ public $newtable;
+ public $tab;
+ public $tabsize = 3;
/**
* @var moodle_url Object representing url for this page
*/
- var $pageurl;
+ public $pageurl;
+
/**
* @var question_category_edit_form Object representing form for adding / editing categories.
*/
- var $catform;
+ public $catform;
/**
* Constructor
@@ -377,7 +380,7 @@ class question_category_object {
/**
* Creates a new category with given params
*/
- public function add_category($newparent, $newcategory, $newinfo, $return = false) {
+ public function add_category($newparent, $newcategory, $newinfo, $return = false, $newinfoformat = FORMAT_HTML) {
global $DB;
if (empty($newcategory)) {
print_error('categorynamecantbeblank', 'question');
@@ -397,6 +400,7 @@ class question_category_object {
$cat->contextid = $contextid;
$cat->name = $newcategory;
$cat->info = $newinfo;
+ $cat->infoformat = $newinfoformat;
$cat->sortorder = 999;
$cat->stamp = make_unique_id_code();
$categoryid = $DB->insert_record("question_categories", $cat);
@@ -410,7 +414,7 @@ class question_category_object {
/**
* Updates an existing category with given params
*/
- public function update_category($updateid, $newparent, $newname, $newinfo) {
+ public function update_category($updateid, $newparent, $newname, $newinfo, $newinfoformat = FORMAT_HTML) {
global $CFG, $DB;
if (empty($newname)) {
print_error('categorynamecantbeblank', 'question');
@@ -442,6 +446,7 @@ class question_category_object {
$cat->id = $updateid;
$cat->name = $newname;
$cat->info = $newinfo;
+ $cat->infoformat = $newinfoformat;
$cat->parent = $parentid;
$cat->contextid = $tocontextid;
$DB->update_record('question_categories', $cat);
diff --git a/question/category_form.php b/question/category_form.php
index 600ee888625..93011aac2be 100644
--- a/question/category_form.php
+++ b/question/category_form.php
@@ -59,14 +59,27 @@ class question_category_edit_form extends moodleform {
$mform->addRule('name', get_string('categorynamecantbeblank', 'question'), 'required', null, 'client');
$mform->setType('name', PARAM_TEXT);
- $mform->addElement('textarea', 'info', get_string('categoryinfo', 'question'), array('rows'=> '10', 'cols'=>'45'));
+ $mform->addElement('editor', 'info', get_string('categoryinfo', 'question'),
+ array('rows' => 10), array('noclean' => 1));
$mform->setDefault('info', '');
- $mform->setType('info', PARAM_TEXT);
+ $mform->setType('info', PARAM_RAW);
$this->add_action_buttons(false, get_string('addcategory', 'question'));
$mform->addElement('hidden', 'id', 0);
$mform->setType('id', PARAM_INT);
}
-}
+ public function set_data($current) {
+ if (is_object($current)) {
+ $current = (array) $current;
+ }
+ if (!empty($current['info'])) {
+ $current['info'] = array('text' => $current['info'],
+ 'infoformat' => $current['infoformat']);
+ } else {
+ $current['info'] = array('text' => '', 'infoformat' => FORMAT_HTML);
+ }
+ parent::set_data($current);
+ }
+}
diff --git a/question/engine/datalib.php b/question/engine/datalib.php
index 47dc925395f..e52b80fe073 100644
--- a/question/engine/datalib.php
+++ b/question/engine/datalib.php
@@ -1279,22 +1279,21 @@ class question_file_saver implements question_response_files {
$string .= $file->get_filepath() . $file->get_filename() . '|' .
$file->get_contenthash() . '|';
}
-
- if ($string) {
- $hash = md5($string);
- } else {
- $hash = '';
- }
+ $hash = md5($string);
if (is_null($text)) {
- return $hash;
+ if ($string) {
+ return $hash;
+ } else {
+ return '';
+ }
}
// We add the file hash so a simple string comparison will say if the
// files have been changed. First strip off any existing file hash.
- $text = preg_replace('/\s*\s*$/', '', $text);
- $text = file_rewrite_urls_to_pluginfile($text, $draftitemid);
- if ($hash) {
+ if ($text !== '') {
+ $text = preg_replace('/\s*\s*$/', '', $text);
+ $text = file_rewrite_urls_to_pluginfile($text, $draftitemid);
$text .= '';
}
return $text;
@@ -1379,6 +1378,41 @@ class question_file_loader implements question_response_files {
public function get_files() {
return $this->step->get_qt_files($this->name, $this->contextid);
}
+
+ /**
+ * Copy these files into a draft area, and return the corresponding
+ * {@link question_file_saver} that can save them again.
+ *
+ * This is used by {@link question_attempt::start_based_on()}, which is used
+ * (for example) by the quizzes 'Each attempt builds on last' feature.
+ *
+ * @return question_file_saver that can re-save these files again.
+ */
+ public function get_question_file_saver() {
+
+ // There are three possibilities here for what $value will look like:
+ // 1) some HTML content followed by an MD5 hash in a HTML comment;
+ // 2) a plain MD5 hash;
+ // 3) or some real content, without any hash.
+ // The problem is that 3) is ambiguous in the case where a student writes
+ // a response that looks exactly like an MD5 hash. For attempts made now,
+ // we avoid case 3) by always going for case 1) or 2) (except when the
+ // response is blank. However, there may be case 3) data in the database
+ // so we need to handle it as best we can.
+ if (preg_match('/\s*\s*$/', $this->value)) {
+ $value = preg_replace('/\s*\s*$/', '', $this->value);
+
+ } else if (preg_match('/^[0-9a-zA-Z]{32}$/', $this->value)) {
+ $value = null;
+
+ } else {
+ $value = $this->value;
+ }
+
+ list($draftid, $text) = $this->step->prepare_response_files_draft_itemid_with_text(
+ $this->name, $this->contextid, $value);
+ return new question_file_saver($draftid, 'question', 'response_' . $this->name, $text);
+ }
}
diff --git a/question/engine/questionattempt.php b/question/engine/questionattempt.php
index eea106944da..e3300c3cb88 100644
--- a/question/engine/questionattempt.php
+++ b/question/engine/questionattempt.php
@@ -923,7 +923,13 @@ class question_attempt {
* @return array name => value pairs.
*/
protected function get_resume_data() {
- return $this->behaviour->get_resume_data();
+ $resumedata = $this->behaviour->get_resume_data();
+ foreach ($resumedata as $name => $value) {
+ if ($value instanceof question_file_loader) {
+ $resumedata[$name] = $value->get_question_file_saver();
+ }
+ }
+ return $resumedata;
}
/**
@@ -975,11 +981,12 @@ class question_attempt {
*/
protected function process_response_files($name, $draftidname, $postdata = null, $text = null) {
if ($postdata) {
- // There can be no files with test data (at the moment).
- return null;
+ // For simulated posts, get the draft itemid from there.
+ $draftitemid = $this->get_submitted_var($draftidname, PARAM_INT, $postdata);
+ } else {
+ $draftitemid = file_get_submitted_draft_itemid($draftidname);
}
- $draftitemid = file_get_submitted_draft_itemid($draftidname);
if (!$draftitemid) {
return null;
}
diff --git a/question/engine/questionattemptstep.php b/question/engine/questionattemptstep.php
index 90ee1d521db..892c738d588 100644
--- a/question/engine/questionattemptstep.php
+++ b/question/engine/questionattemptstep.php
@@ -106,7 +106,7 @@ class question_attempt_step {
global $USER;
if (!is_array($data)) {
- echo format_backtrace(debug_backtrace());
+ throw new coding_exception('$data must be an array when constructing a question_attempt_step.');
}
$this->state = question_state::$unprocessed;
$this->data = $data;
diff --git a/question/type/essay/question.php b/question/type/essay/question.php
index 29a4fc742e4..a9b7cc2d0b2 100644
--- a/question/type/essay/question.php
+++ b/question/type/essay/question.php
@@ -87,12 +87,12 @@ class qtype_essay_question extends question_with_responses {
public function is_same_response(array $prevresponse, array $newresponse) {
if (array_key_exists('answer', $prevresponse) && $prevresponse['answer'] !== $this->responsetemplate) {
- $value1 = $prevresponse['answer'];
+ $value1 = (string) $prevresponse['answer'];
} else {
$value1 = '';
}
if (array_key_exists('answer', $newresponse) && $newresponse['answer'] !== $this->responsetemplate) {
- $value2 = $newresponse['answer'];
+ $value2 = (string) $newresponse['answer'];
} else {
$value2 = '';
}
diff --git a/question/type/essay/tests/helper.php b/question/type/essay/tests/helper.php
index 02e7d74a6ab..fd89b87f600 100644
--- a/question/type/essay/tests/helper.php
+++ b/question/type/essay/tests/helper.php
@@ -78,6 +78,29 @@ class qtype_essay_test_helper extends question_test_helper {
return $q;
}
+ /**
+ * Make the data what would be received from the editing form for an essay
+ * question using the HTML editor allowing embedded files as input, and up
+ * to three attachments.
+ *
+ * @return stdClass the data that would be returned by $form->get_gata();
+ */
+ public function get_essay_question_form_data_editorfilepicker() {
+ $fromform = new stdClass();
+
+ $fromform->name = 'Essay question with filepicker and attachments';
+ $fromform->questiontext = array('text' => 'Please write a story about a frog.', 'format' => FORMAT_HTML);
+ $fromform->defaultmark = 1.0;
+ $fromform->generalfeedback = array('text' => 'I hope your story had a beginning, a middle and an end.', 'format' => FORMAT_HTML);
+ $fromform->responseformat = 'editorfilepicker';
+ $fromform->responsefieldlines = 10;
+ $fromform->attachments = 3;
+ $fromform->graderinfo = array('text' => '', 'format' => FORMAT_HTML);
+ $fromform->responsetemplate = array('text' => '', 'format' => FORMAT_HTML);
+
+ return $fromform;
+ }
+
/**
* Makes an essay question using plain text input.
* @return qtype_essay_question
diff --git a/question/type/essay/tests/question_test.php b/question/type/essay/tests/question_test.php
index 856619bd78b..bd9cdb732e2 100644
--- a/question/type/essay/tests/question_test.php
+++ b/question/type/essay/tests/question_test.php
@@ -36,7 +36,7 @@ require_once($CFG->dirroot . '/question/engine/tests/helpers.php');
* @copyright 2009 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-class qtype_essay_question_test extends advanced_testcase {
+class qtype_essay_question_testcase extends advanced_testcase {
public function test_get_question_summary() {
$essay = test_question_maker::make_an_essay_question();
$essay->questiontext = 'Hello ';
@@ -46,8 +46,8 @@ class qtype_essay_question_test extends advanced_testcase {
public function test_summarise_response() {
$longstring = str_repeat('0123456789', 50);
$essay = test_question_maker::make_an_essay_question();
- $this->assertEquals($longstring,
- $essay->summarise_response(array('answer' => $longstring, 'answerformat' => FORMAT_PLAIN)));
+ $this->assertEquals($longstring, $essay->summarise_response(
+ array('answer' => $longstring, 'answerformat' => FORMAT_HTML)));
}
public function test_is_same_response() {
diff --git a/question/type/essay/tests/walkthrough_test.php b/question/type/essay/tests/walkthrough_test.php
index 39ad956bdce..e64149212cd 100644
--- a/question/type/essay/tests/walkthrough_test.php
+++ b/question/type/essay/tests/walkthrough_test.php
@@ -35,7 +35,7 @@ require_once($CFG->dirroot . '/question/engine/tests/helpers.php');
* @copyright 2013 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-class qtype_essay_walkthrough_test extends qbehaviour_walkthrough_test_base {
+class qtype_essay_walkthrough_testcase extends qbehaviour_walkthrough_test_base {
protected function check_contains_textarea($name, $content = '', $height = 10) {
$fieldname = $this->quba->get_field_prefix($this->slot) . $name;
@@ -50,6 +50,28 @@ class qtype_essay_walkthrough_test extends qbehaviour_walkthrough_test_base {
}
}
+ /**
+ * Helper method: Store a test file with a given name and contents in a
+ * draft file area.
+ *
+ * @param int $usercontextid user context id.
+ * @param int $draftitemid draft item id.
+ * @param string $filename filename.
+ * @param string $contents file contents.
+ */
+ protected function save_file_to_draft_area($usercontextid, $draftitemid, $filename, $contents) {
+ $fs = get_file_storage();
+
+ $filerecord = new stdClass();
+ $filerecord->contextid = $usercontextid;
+ $filerecord->component = 'user';
+ $filerecord->filearea = 'draft';
+ $filerecord->itemid = $draftitemid;
+ $filerecord->filepath = '/';
+ $filerecord->filename = $filename;
+ $fs->create_file_from_string($filerecord, $contents);
+ }
+
public function test_deferred_feedback_html_editor() {
// Create an essay question.
@@ -204,4 +226,199 @@ class qtype_essay_walkthrough_test extends qbehaviour_walkthrough_test_base {
$this->get_contains_question_text_expectation($q),
$this->get_contains_general_feedback_expectation($q));
}
+
+ public function test_deferred_feedback_html_editor_with_files_attempt_on_last() {
+ global $CFG, $USER;
+
+ $this->resetAfterTest(true);
+ $this->setAdminUser();
+ $usercontextid = context_user::instance($USER->id)->id;
+ $fs = get_file_storage();
+
+ // Create an essay question in the DB.
+ $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
+ $cat = $generator->create_question_category();
+ $question = $generator->create_question('essay', 'editorfilepicker', array('category' => $cat->id));
+
+ // Start attempt at the question.
+ $q = question_bank::load_question($question->id);
+ $this->start_attempt_at_question($q, 'deferredfeedback', 1);
+
+ $this->check_current_state(question_state::$todo);
+ $this->check_current_mark(null);
+ $this->check_step_count(1);
+
+ // Process a response and check the expected result.
+ // First we need to get the draft item ids.
+ $this->render();
+ if (!preg_match('/env=editor&.*?itemid=(\d+)&/', $this->currentoutput, $matches)) {
+ throw new coding_exception('Editor draft item id not found.');
+ }
+ $editordraftid = $matches[1];
+ if (!preg_match('/env=filemanager&action=browse&.*?itemid=(\d+)&/', $this->currentoutput, $matches)) {
+ throw new coding_exception('File manager draft item id not found.');
+ }
+ $attachementsdraftid = $matches[1];
+
+ $this->save_file_to_draft_area($usercontextid, $editordraftid, 'smile.txt', ':-)');
+ $this->save_file_to_draft_area($usercontextid, $attachementsdraftid, 'greeting.txt', 'Hello world!');
+ $this->process_submission(array(
+ 'answer' => 'Here is a picture: .',
+ 'answerformat' => FORMAT_HTML,
+ 'answer:itemid' => $editordraftid,
+ 'attachments' => $attachementsdraftid));
+
+ $this->check_current_state(question_state::$complete);
+ $this->check_current_mark(null);
+ $this->check_step_count(2);
+ $this->save_quba();
+
+ // Save the same response again, and verify no new step is created.
+ $this->load_quba();
+
+ $this->render();
+ if (!preg_match('/env=editor&.*?itemid=(\d+)&/', $this->currentoutput, $matches)) {
+ throw new coding_exception('Editor draft item id not found.');
+ }
+ $editordraftid = $matches[1];
+ if (!preg_match('/env=filemanager&action=browse&.*?itemid=(\d+)&/', $this->currentoutput, $matches)) {
+ throw new coding_exception('File manager draft item id not found.');
+ }
+ $attachementsdraftid = $matches[1];
+
+ $this->process_submission(array(
+ 'answer' => 'Here is a picture: .',
+ 'answerformat' => FORMAT_HTML,
+ 'answer:itemid' => $editordraftid,
+ 'attachments' => $attachementsdraftid));
+
+ $this->check_current_state(question_state::$complete);
+ $this->check_current_mark(null);
+ $this->check_step_count(2);
+
+ // Now submit all and finish.
+ $this->finish();
+ $this->check_current_state(question_state::$needsgrading);
+ $this->check_current_mark(null);
+ $this->check_step_count(3);
+ $this->save_quba();
+
+ // Now start a new attempt based on the old one.
+ $this->load_quba();
+ $oldqa = $this->get_question_attempt();
+
+ $q = question_bank::load_question($question->id);
+ $this->quba = question_engine::make_questions_usage_by_activity('unit_test',
+ context_system::instance());
+ $this->quba->set_preferred_behaviour('deferredfeedback');
+ $this->slot = $this->quba->add_question($q, 1);
+ $this->quba->start_question_based_on($this->slot, $oldqa);
+
+ $this->check_current_state(question_state::$complete);
+ $this->check_current_mark(null);
+ $this->check_step_count(1);
+ $this->save_quba();
+
+ // Now save the same response again, and ensure that a new step is not created.
+ $this->load_quba();
+
+ $this->render();
+ if (!preg_match('/env=editor&.*?itemid=(\d+)&/', $this->currentoutput, $matches)) {
+ throw new coding_exception('Editor draft item id not found.');
+ }
+ $editordraftid = $matches[1];
+ if (!preg_match('/env=filemanager&action=browse&.*?itemid=(\d+)&/', $this->currentoutput, $matches)) {
+ throw new coding_exception('File manager draft item id not found.');
+ }
+ $attachementsdraftid = $matches[1];
+
+ $this->process_submission(array(
+ 'answer' => 'Here is a picture: .',
+ 'answerformat' => FORMAT_HTML,
+ 'answer:itemid' => $editordraftid,
+ 'attachments' => $attachementsdraftid));
+
+ $this->check_current_state(question_state::$complete);
+ $this->check_current_mark(null);
+ $this->check_step_count(1);
+ }
+
+ public function test_deferred_feedback_html_editor_with_files_attempt_on_last_no_files_uploaded() {
+ global $CFG, $USER;
+
+ $this->resetAfterTest(true);
+ $this->setAdminUser();
+ $usercontextid = context_user::instance($USER->id)->id;
+ $fs = get_file_storage();
+
+ // Create an essay question in the DB.
+ $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
+ $cat = $generator->create_question_category();
+ $question = $generator->create_question('essay', 'editorfilepicker', array('category' => $cat->id));
+
+ // Start attempt at the question.
+ $q = question_bank::load_question($question->id);
+ $this->start_attempt_at_question($q, 'deferredfeedback', 1);
+
+ $this->check_current_state(question_state::$todo);
+ $this->check_current_mark(null);
+ $this->check_step_count(1);
+
+ // Process a response and check the expected result.
+ // First we need to get the draft item ids.
+ $this->render();
+ if (!preg_match('/env=editor&.*?itemid=(\d+)&/', $this->currentoutput, $matches)) {
+ throw new coding_exception('Editor draft item id not found.');
+ }
+ $editordraftid = $matches[1];
+ if (!preg_match('/env=filemanager&action=browse&.*?itemid=(\d+)&/', $this->currentoutput, $matches)) {
+ throw new coding_exception('File manager draft item id not found.');
+ }
+ $attachementsdraftid = $matches[1];
+
+ $this->process_submission(array(
+ 'answer' => 'I refuse to draw you a picture, so there!',
+ 'answerformat' => FORMAT_HTML,
+ 'answer:itemid' => $editordraftid,
+ 'attachments' => $attachementsdraftid));
+
+ $this->check_current_state(question_state::$complete);
+ $this->check_current_mark(null);
+ $this->check_step_count(2);
+ $this->save_quba();
+
+ // Now submit all and finish.
+ $this->finish();
+ $this->check_current_state(question_state::$needsgrading);
+ $this->check_current_mark(null);
+ $this->check_step_count(3);
+ $this->save_quba();
+
+ // Now start a new attempt based on the old one.
+ $this->load_quba();
+ $oldqa = $this->get_question_attempt();
+
+ $q = question_bank::load_question($question->id);
+ $this->quba = question_engine::make_questions_usage_by_activity('unit_test',
+ context_system::instance());
+ $this->quba->set_preferred_behaviour('deferredfeedback');
+ $this->slot = $this->quba->add_question($q, 1);
+ $this->quba->start_question_based_on($this->slot, $oldqa);
+
+ $this->check_current_state(question_state::$complete);
+ $this->check_current_mark(null);
+ $this->check_step_count(1);
+ $this->save_quba();
+
+ // Check the display.
+ $this->load_quba();
+ $this->render();
+ $this->assertRegExp('/I refuse to draw you a picture, so there!/', $this->currentoutput);
+ }
}
diff --git a/question/type/match/db/upgrade.php b/question/type/match/db/upgrade.php
index ff85c2ac9f9..42bfd0429b1 100644
--- a/question/type/match/db/upgrade.php
+++ b/question/type/match/db/upgrade.php
@@ -44,6 +44,25 @@ function xmldb_qtype_match_upgrade($oldversion) {
// Moodle v2.4.0 release upgrade line.
// Put any upgrade step following this.
+ if ($oldversion < 2013012099) {
+ // Find duplicate rows before they break the 2013012103 step below.
+ $problemids = $DB->get_recordset_sql("
+ SELECT question, MIN(id) AS recordidtokeep
+ FROM {question_match}
+ GROUP BY question
+ HAVING COUNT(1) > 1
+ ");
+ foreach ($problemids as $problem) {
+ $DB->delete_records_select('question_match',
+ 'question = ? AND id > ?',
+ array($problem->question, $problem->recordidtokeep));
+ }
+ $problemids->close();
+
+ // Shortanswer savepoint reached.
+ upgrade_plugin_savepoint(true, 2013012099, 'qtype', 'match');
+ }
+
if ($oldversion < 2013012100) {
// Define table question_match to be renamed to qtype_match_options.
diff --git a/question/type/multianswer/module.js b/question/type/multianswer/module.js
index 2fbe4e061d3..80cfde66715 100644
--- a/question/type/multianswer/module.js
+++ b/question/type/multianswer/module.js
@@ -27,7 +27,7 @@ M.qtype_multianswer = M.qtype_multianswer || {};
M.qtype_multianswer.init = function (Y, questiondiv) {
- Y.one(questiondiv).all('span.subquestion').each(function(subqspan, i) {
+ Y.one(questiondiv).all('span.subquestion').each(function(subqspan) {
var feedbackspan = subqspan.one('.feedbackspan');
if (!feedbackspan) {
return;
@@ -39,7 +39,9 @@ M.qtype_multianswer.init = function (Y, questiondiv) {
align: {
node: subqspan,
points: [Y.WidgetPositionAlign.TC, Y.WidgetPositionAlign.BC]
- }
+ },
+ constrain: subqspan.ancestor('div.que'),
+ preventOverlap: true
});
overlay.render();
diff --git a/question/type/multianswer/styles.css b/question/type/multianswer/styles.css
index aeeaccf6b94..3916310b301 100644
--- a/question/type/multianswer/styles.css
+++ b/question/type/multianswer/styles.css
@@ -1,10 +1,17 @@
.que.multianswer .feedbackspan {
display: block;
+ max-width: 70%;
background: #fff3bf;
padding: 0.5em;
margin-top: 1em;
box-shadow: 0.5em 0.5em 1em #000000;
}
+body.ie6 .que.multianswer .feedbackspan,
+body.ie7 .que.multianswer .feedbackspan,
+body.ie8 .que.multianswer .feedbackspan,
+body.ie9 .que.multianswer .feedbackspan {
+ width: 70%;
+}
.que.multianswer .answer .specificfeedback {
display: inline;
padding: 0 0.7em;
diff --git a/question/type/numerical/edit_numerical_form.php b/question/type/numerical/edit_numerical_form.php
index 2554177cc2e..e99cdd6551e 100644
--- a/question/type/numerical/edit_numerical_form.php
+++ b/question/type/numerical/edit_numerical_form.php
@@ -186,8 +186,6 @@ class qtype_numerical_edit_form extends question_edit_form {
protected function unit_group($mform) {
$grouparray = array();
$grouparray[] = $mform->createElement('text', 'unit', get_string('unit', 'quiz'), array('size'=>10));
- $grouparray[] = $mform->createElement('static', '', '', ' ' .
- get_string('multiplier', 'quiz').' ');
$grouparray[] = $mform->createElement('text', 'multiplier',
get_string('multiplier', 'quiz'), array('size'=>10));
diff --git a/question/type/numerical/styles.css b/question/type/numerical/styles.css
index c6b40614f5c..9ef422e8889 100644
--- a/question/type/numerical/styles.css
+++ b/question/type/numerical/styles.css
@@ -34,6 +34,8 @@ body#page-question-type-numerical div[id^=fgroup_id_][id*=answeroptions_] .fgrou
font-weight: bold;
}
+body.path-question-type div#fgroup_id_penaltygrp label[for^=id_unitpenalty],
+body.path-question-type div[id^=fgroup_id_units_] label[for^='id_unit_'],
body#page-question-type-numerical div[id^=fgroup_id_][id*=answeroptions_] label[for^='id_answer_']{
position: absolute;
left: -10000px;
diff --git a/report/performance/locallib.php b/report/performance/locallib.php
index b14a1275680..993054638bb 100644
--- a/report/performance/locallib.php
+++ b/report/performance/locallib.php
@@ -209,13 +209,9 @@ class report_performance {
DEBUG_NORMAL => 'debugnormal',
DEBUG_ALL => 'debugall',
DEBUG_DEVELOPER => 'debugdeveloper');
- // If debug is not set then consider it as 0.
- if (!isset($CFG->themedesignermode)) {
- $CFG->debug = DEBUG_NONE;
- }
$issueresult->statusstr = get_string($debugchoices[$CFG->debug], 'admin');
- if ($CFG->debug != DEBUG_DEVELOPER) {
+ if (!$CFG->debugdeveloper) {
$issueresult->status = self::REPORT_PERFORMANCE_OK;
$issueresult->comment = get_string('check_debugmsg_comment_nodeveloper', 'report_performance');
} else {
diff --git a/repository/skydrive/lib.php b/repository/skydrive/lib.php
index 50b69333903..26464b8de66 100644
--- a/repository/skydrive/lib.php
+++ b/repository/skydrive/lib.php
@@ -96,7 +96,11 @@ class repository_skydrive extends repository {
$ret['dynload'] = true;
$ret['nosearch'] = true;
$ret['manage'] = 'https://skydrive.live.com/';
- $ret['list'] = $this->skydrive->get_file_list($path);
+
+ $fileslist = $this->skydrive->get_file_list($path);
+ // Filter list for accepted types. Hopefully this will be done by core some day.
+ $fileslist = array_filter($fileslist, array($this, 'filter'));
+ $ret['list'] = $fileslist;
// Generate path bar, always start with the plugin name.
$ret['path'] = array();
diff --git a/tag/coursetags_more.php b/tag/coursetags_more.php
index cbfeaefd6d9..9b65813de1f 100644
--- a/tag/coursetags_more.php
+++ b/tag/coursetags_more.php
@@ -97,66 +97,24 @@ echo $OUTPUT->heading($title, 2, 'centre');
// Prepare data for tags
$courselink = '';
-if ($courseid) { $courselink = '&courseid='.$courseid; }
+if ($courseid) {
+ $courselink = '&courseid='.$courseid;
+}
$myurl = $CFG->wwwroot.'/tag/coursetags_more.php';
$myurl2 = $CFG->wwwroot.'/tag/coursetags_more.php?show='.$show;
-// Course tags
-if ($show == 'course' and $courseid) {
-
- if ($sort == 'popularity') {
- $tags = tag_print_cloud(coursetag_get_tags($courseid, 0, '', 0, 'popularity'), 150, true);
- } else if ($sort == 'date') {
- $tags = tag_print_cloud(coursetag_get_tags($courseid, 0, '', 0, 'timemodified'), 150, true);
- } else {
- $tags = tag_print_cloud(coursetag_get_tags($courseid, 0, '', 0, 'name'), 150, true);
- }
-
-// My tags
-} else if ($show == 'my' and $loggedin) {
-
- if ($sort == 'popularity') {
- $tags = tag_print_cloud(coursetag_get_tags(0, $USER->id, 'default', 0, 'popularity'), 150, true);
- } else if ($sort == 'date') {
- $tags = tag_print_cloud(coursetag_get_tags(0, $USER->id, 'default', 0, 'timemodified'), 150, true);
- } else {
- $tags = tag_print_cloud(coursetag_get_tags(0, $USER->id, 'default', 0, 'name'), 150, true);
- }
-
-// Official course tags
-} else if ($show == 'official') {
-
- if ($sort == 'popularity') {
- $tags = tag_print_cloud(coursetag_get_tags(0, 0, 'official', 0, 'popularity'), 150, true);
- } else if ($sort == 'date') {
- $tags = tag_print_cloud(coursetag_get_tags(0, 0, 'official', 0, 'timemodified'), 150, true);
- } else {
- $tags = tag_print_cloud(coursetag_get_tags(0, 0, 'official', 0, 'name'), 150, true);
- }
-
-// Community (official and personal together) also called user tags
-} else if ($show == 'community') {
-
- if ($sort == 'popularity') {
- $tags = tag_print_cloud(coursetag_get_tags(0, 0, 'default', 0, 'popularity'), 150, true);
- } else if ($sort == 'date') {
- $tags = tag_print_cloud(coursetag_get_tags(0, 0, 'default', 0, 'timemodified'), 150, true);
- } else {
- $tags = tag_print_cloud(coursetag_get_tags(0, 0, 'default', 0, 'name'), 150, true);
- }
-
-// All tags for courses and blogs and any thing else tagged - the fallback default ($show == all)
+if ($show == 'course' and $courseid) { // Course tags.
+ $tags = tag_print_cloud(coursetag_get_tags($courseid, 0, ''), 150, true, $sort);
+} else if ($show == 'my' and $loggedin) { // My tags.
+ $tags = tag_print_cloud(coursetag_get_tags(0, $USER->id, 'default'), 150, true, $sort);
+} else if ($show == 'official') { // Official course tags.
+ $tags = tag_print_cloud(coursetag_get_tags(0, 0, 'official'), 150, true, $sort);
+} else if ($show == 'community') { // Community (official and personal together) also called user tags.
+ $tags = tag_print_cloud(coursetag_get_tags(0, 0, 'default'), 150, true, $sort);
} else {
-
+ // All tags for courses and blogs and any thing else tagged - the fallback default ($show == all).
$subtitle = $showalltags;
- if ($sort == 'popularity') {
- $tags = tag_print_cloud(coursetag_get_all_tags('popularity'), 150, true);
- } else if ($sort == 'date') {
- $tags = tag_print_cloud(coursetag_get_all_tags('timemodified'), 150, true);
- } else {
- $tags = tag_print_cloud(coursetag_get_all_tags('name'), 150, true);
- }
-
+ $tags = tag_print_cloud(coursetag_get_all_tags(), 150, true, $sort);
}
// Prepare the links for the show and order lines
@@ -209,16 +167,18 @@ if ($sort == 'date') {
// Prepare output
$fclass = '';
// make the tags larger when there are not so many
-if (strlen($tags) < 10000) { $fclass = 'coursetag_more_large'; }
+if (strlen($tags) < 10000) {
+ $fclass = 'coursetag_more_large';
+}
$outstr = '
-
-
'.$welcome.'
-
'.$link1.'
-
'.$link2.'
-
-
'.
- $tags.'
-
';
+
+
'.$welcome.'
+
'.$link1.'
+
'.$link2.'
+
+
'.
+$tags.'
+
';
echo $outstr;
echo $OUTPUT->footer();
diff --git a/tag/coursetagslib.php b/tag/coursetagslib.php
index f2814186d7d..53d4ff045b3 100644
--- a/tag/coursetagslib.php
+++ b/tag/coursetagslib.php
@@ -37,10 +37,10 @@ require_once $CFG->dirroot.'/tag/locallib.php';
* @param string $tagtype (optional) The type of tag, empty string returns all types. Currently (Moodle 2.2) there are two
* types of tags which are used within Moodle, they are 'official' and 'default'.
* @param int $numtags (optional) number of tags to display, default of 80 is set in the block, 0 returns all
- * @param string $sort (optional) selected sorting, default is alpha sort (name) also timemodified or popularity
+ * @param string $unused (optional) was selected sorting, moved to tag_print_cloud()
* @return array
*/
-function coursetag_get_tags($courseid, $userid=0, $tagtype='', $numtags=0, $sort='name') {
+function coursetag_get_tags($courseid, $userid=0, $tagtype='', $numtags=0, $unused = '') {
global $CFG, $DB;
@@ -96,11 +96,6 @@ function coursetag_get_tags($courseid, $userid=0, $tagtype='', $numtags=0, $sort
// prepare the return
$return = array();
if ($tags) {
- // sort the tag display order
- if ($sort != 'popularity') {
- $CFG->tagsort = $sort;
- usort($tags, "coursetag_sort");
- }
// avoid print_tag_cloud()'s ksort upsetting ordering by setting the key here
foreach ($tags as $value) {
$return[] = $value;
@@ -117,11 +112,11 @@ function coursetag_get_tags($courseid, $userid=0, $tagtype='', $numtags=0, $sort
*
* @package core_tag
* @category tag
- * @param string $sort (optional) selected sorting, default is alpha sort (name) also timemodified or popularity
+ * @param string $unused (optional) was selected sorting - moved to tag_print_cloud()
* @param int $numtags (optional) number of tags to display, default of 20 is set in the block, 0 returns all
* @return array
*/
-function coursetag_get_all_tags($sort='name', $numtags=0) {
+function coursetag_get_all_tags($unused='', $numtags=0) {
global $CFG, $DB;
@@ -145,10 +140,6 @@ function coursetag_get_all_tags($sort='name', $numtags=0) {
$return = array();
if ($tags) {
- if ($sort != 'popularity') {
- $CFG->tagsort = $sort;
- usort($tags, "coursetag_sort");
- }
foreach ($tags as $value) {
$return[] = $value;
}
@@ -157,43 +148,6 @@ function coursetag_get_all_tags($sort='name', $numtags=0) {
return $return;
}
-/**
- * Sorting callback function for coursetag_get_tags() and coursetag_get_all_tags() only
- *
- * This function does a comparision on a field withing two variables, $a and $b. The field used is specified by
- * $CFG->tagsort or we just use the 'name' field if $CFG->tagsort is empty. The comparison works as follows:
- * If $a->$tagsort is greater than $b->$tagsort, 1 is returned.
- * If $a->$tagsort is equal to $b->$tagsort, 0 is returned.
- * If $a->$tagsort is less than $b->$tagsort, -1 is returned.
- *
- * Also if $a->$tagsort is not numeric or a string, 0 is returned.
- *
- * @package core_tag
- * @access private
- * @param int|string|mixed $a Variable to compare against $b
- * @param int|string|mixed $b Variable to compare against $a
- * @return int The result of the comparison/validation 1, 0 or -1
- */
-function coursetag_sort($a, $b) {
- // originally from block_blog_tags
- global $CFG;
-
- // set up the variable $tagsort as either 'name' or 'timemodified' only, 'popularity' does not need sorting
- if (empty($CFG->tagsort)) {
- $tagsort = 'name';
- } else {
- $tagsort = $CFG->tagsort;
- }
-
- if (is_numeric($a->$tagsort)) {
- return ($a->$tagsort == $b->$tagsort) ? 0 : ($a->$tagsort > $b->$tagsort) ? 1 : -1;
- } else if (is_string($a->$tagsort)) {
- return strcmp($a->$tagsort, $b->$tagsort);
- } else {
- return 0;
- }
-}
-
/**
* Returns javascript for use in tags block and supporting pages
*
diff --git a/tag/locallib.php b/tag/locallib.php
index db00484df09..55cd31e278a 100644
--- a/tag/locallib.php
+++ b/tag/locallib.php
@@ -35,9 +35,10 @@ require_once($CFG->libdir.'/filelib.php');
* @param array $tagset Array of tags to display
* @param int $nr_of_tags Limit for the number of tags to return/display, used if $tagset is null
* @param bool $return if true the function will return the generated tag cloud instead of displaying it.
+ * @param string $sort (optional) selected sorting, default is alpha sort (name) also timemodified or popularity
* @return string|null a HTML string or null if this function does the output
*/
-function tag_print_cloud($tagset=null, $nr_of_tags=150, $return=false) {
+function tag_print_cloud($tagset=null, $nr_of_tags=150, $return=false, $sort='') {
global $CFG, $DB;
$can_manage_tags = has_capability('moodle/tag:manage', context_system::instance());
@@ -69,7 +70,19 @@ function tag_print_cloud($tagset=null, $nr_of_tags=150, $return=false) {
$etags[] = $tag;
}
+ // Set up sort global - used to pass sort type into tag_cloud_sort through usort() avoiding multiple sort functions.
+ // TODO make calling functions pass 'count' or 'timemodified' not 'popularity' or 'date'.
+ $oldsort = empty($CFG->tagsort) ? null : $CFG->tagsort;
+ if ($sort == 'popularity') {
+ $CFG->tagsort = 'count';
+ } else if ($sort == 'date') {
+ $CFG->tagsort = 'timemodified';
+ } else {
+ $CFG->tagsort = 'name';
+ }
usort($etags, "tag_cloud_sort");
+ $CFG->tagsort = $oldsort;
+
$output = '';
$output .= "\n