Merge branch 'MDL-38197-master' of git://github.com/sammarshallou/moodle
authorDan Poltawski <dan@moodle.com>
Tue, 20 Aug 2013 06:55:07 +0000 (14:55 +0800)
committerDan Poltawski <dan@moodle.com>
Tue, 20 Aug 2013 06:55:07 +0000 (14:55 +0800)
123 files changed:
admin/cli/automated_backups.php
admin/cli/install.php
admin/tool/generator/cli/generate.php
admin/tool/generator/index.php
admin/tool/phpunit/webrunner.php
auth/cas/cli/sync_users.php
auth/ldap/cli/sync_users.php
backup/import.php
backup/moodle2/restore_qtype_plugin.class.php
backup/util/factories/backup_factory.class.php
backup/util/factories/tests/factories_test.php
backup/util/plan/restore_plan.class.php
blocks/moodleblock.class.php
blocks/tags/block_tags.php
cache/tests/administration_helper_test.php
cohort/lib.php
cohort/tests/cohortlib_test.php
course/delete.php
course/editsection.php
course/format/renderer.php
course/lib.php
course/loginas.php
course/manage.php
course/tests/courselib_test.php
course/yui/dragdrop/dragdrop.js
enrol/ldap/cli/sync.php
enrol/yui/rolemanager/assets/skins/sam/rolemanager.css
enrol/yui/rolemanager/rolemanager.js
grade/grading/form/rubric/styles.css
install.php
lang/en/auth.php
lang/en/cohort.php
lang/en/moodle.php
lib/accesslib.php
lib/adminlib.php
lib/badgeslib.php
lib/classes/component.php
lib/classes/event/base.php
lib/classes/event/cohort_created.php [new file with mode: 0644]
lib/classes/event/cohort_deleted.php [new file with mode: 0644]
lib/classes/event/cohort_member_added.php [new file with mode: 0644]
lib/classes/event/cohort_member_removed.php [new file with mode: 0644]
lib/classes/event/cohort_updated.php [new file with mode: 0644]
lib/classes/event/course_category_deleted.php [new file with mode: 0644]
lib/classes/event/course_content_deleted.php [new file with mode: 0644]
lib/classes/event/course_created.php [new file with mode: 0644]
lib/classes/event/course_deleted.php [new file with mode: 0644]
lib/classes/event/course_restored.php [new file with mode: 0644]
lib/classes/event/course_section_updated.php [new file with mode: 0644]
lib/classes/event/course_updated.php [new file with mode: 0644]
lib/classes/event/user_loggedinas.php [new file with mode: 0644]
lib/coursecatlib.php
lib/cronlib.php
lib/dml/mssql_native_moodle_database.php
lib/dml/pdo_moodle_database.php
lib/dml/sqlsrv_native_moodle_database.php
lib/dml/tests/dml_test.php
lib/editor/tinymce/classes/plugin.php
lib/editor/tinymce/cli/update_lang_files.php
lib/editor/tinymce/lib.php
lib/filestorage/file_storage.php
lib/formslib.php
lib/installlib.php
lib/minify/config.php
lib/modinfolib.php
lib/moodlelib.php
lib/outputlib.php
lib/outputrenderers.php
lib/outputrequirementslib.php
lib/phpunit/bootstrap.php
lib/phpunit/classes/advanced_testcase.php
lib/phpunit/classes/util.php
lib/phpunit/tests/advanced_test.php
lib/sessionlib.php
lib/setup.php
lib/setuplib.php
lib/simplepie/moodle_simplepie.php
lib/tests/event_test.php
lib/tests/sessionlib_test.php [new file with mode: 0644]
lib/tests/statslib_test.php
lib/upgrade.txt
lib/upgradelib.php
lib/weblib.php
lib/yui/build/moodle-core-blocks/moodle-core-blocks-debug.js
lib/yui/build/moodle-core-blocks/moodle-core-blocks-min.js
lib/yui/build/moodle-core-blocks/moodle-core-blocks.js
lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue-debug.js
lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue-min.js
lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue.js
lib/yui/dragdrop/dragdrop.js
lib/yui/src/blocks/js/blocks.js
lib/yui/src/notification/js/dialogue.js
mod/scorm/datamodels/aicclib.php
mod/scorm/db/upgrade.php
mod/scorm/version.php
mod/workshop/renderer.php
question/behaviour/manualgraded/tests/walkthrough_test.php
question/category.php
question/category_class.php
question/category_form.php
question/engine/datalib.php
question/engine/questionattempt.php
question/engine/questionattemptstep.php
question/type/essay/question.php
question/type/essay/tests/helper.php
question/type/essay/tests/question_test.php
question/type/essay/tests/walkthrough_test.php
question/type/match/db/upgrade.php
question/type/multianswer/module.js
question/type/multianswer/styles.css
question/type/numerical/edit_numerical_form.php
question/type/numerical/styles.css
report/performance/locallib.php
tag/coursetags_more.php
tag/coursetagslib.php
tag/locallib.php
tag/upgrade.txt
theme/base/style/admin.css
theme/base/style/core.css
theme/bootstrapbase/less/moodle/core.less
theme/bootstrapbase/less/moodle/forms.less
theme/bootstrapbase/less/moodle/question.less
theme/bootstrapbase/style/moodle.css

index dc80c7a..3c3841e 100644 (file)
@@ -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();
index 21617cf..fcd25b2 100644 (file)
@@ -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);
index 4514fd7..353883e 100644 (file)
@@ -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);
 }
index f77c8e0..7a917e9 100644 (file)
@@ -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!!!');
 }
 
index f697f7e..f52bf6b 100644 (file)
@@ -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();
 
index 33cf33a..a02c32f 100644 (file)
@@ -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'));
index ee15538..6898293 100644 (file)
@@ -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'));
index 6dd5038..fbd919a 100644 (file)
@@ -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);
-
-            echo $OUTPUT->header();
-            echo $renderer->precheck_notices($precheckresults);
-            echo $OUTPUT->continue_button(new moodle_url('/course/view.php', array('id'=>$course->id)));
-            echo $OUTPUT->footer();
-            die();
+        if (is_array($precheckresults)) {
+            if (!empty($precheckresults['errors'])) { // If errors are found, terminate the import.
+                fulldelete($tempdestination);
+
+                echo $OUTPUT->header();
+                echo $renderer->precheck_notices($precheckresults);
+                echo $OUTPUT->continue_button(new moodle_url('/course/view.php', array('id'=>$course->id)));
+                echo $OUTPUT->footer();
+                die();
+            }
+            if (!empty($precheckresults['warnings'])) { // If warnings are found, go ahead but display warnings later.
+                $warnings = $precheckresults['warnings'];
+            }
         }
-    } else {
-        if ($restoretarget == backup::TARGET_CURRENT_DELETING || $restoretarget == backup::TARGET_EXISTING_DELETING) {
-            restore_dbops::delete_course_content($course->id);
-        }
-        // Execute the restore
-        $rc->execute_plan();
     }
+    if ($restoretarget == backup::TARGET_CURRENT_DELETING || $restoretarget == backup::TARGET_EXISTING_DELETING) {
+        restore_dbops::delete_course_content($course->id);
+    }
+    // Execute the restore.
+    $rc->execute_plan();
 
     // Delete the temp directory now
     fulldelete($tempdestination);
 
     // Display a notification and a continue button
     echo $OUTPUT->header();
-    echo $OUTPUT->notification(get_string('importsuccess', 'backup'),'notifysuccess');
+    if ($warnings) {
+        echo $OUTPUT->box_start();
+        echo $OUTPUT->notification(get_string('warning'), 'notifywarning');
+        echo html_writer::start_tag('ul', array('class'=>'list'));
+        foreach ($warnings as $warning) {
+            echo html_writer::tag('li', $warning);
+        }
+        echo html_writer::end_tag('ul');
+        echo $OUTPUT->box_end();
+    }
+    echo $OUTPUT->notification(get_string('importsuccess', 'backup'), 'notifysuccess');
     echo $OUTPUT->continue_button(new moodle_url('/course/view.php', array('id'=>$course->id)));
     echo $OUTPUT->footer();
 
index 99365ba..60f599a 100644 (file)
@@ -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);
index e2143cd..6f8ba31 100644 (file)
@@ -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;
         }
 
index 54dc4e9..2a3fa1a 100644 (file)
@@ -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
index 9943e0b..c9dd8fb 100644 (file)
@@ -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();
     }
 
     /**
index 9d9b289..461baf1 100644 (file)
@@ -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';
         }
index 1035614..6b1a793 100644 (file)
@@ -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) {
index 6a1630d..c30ae0c 100644 (file)
@@ -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;
     }
 }
index 61d56c9..51ac01c 100644 (file)
@@ -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();
 }
 
 /**
index e6446e7..64f40c7 100644 (file)
@@ -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);
+    }
+
+    public function test_cohort_add_cohort_missing_name() {
+        $cohort = new stdClass();
+        $cohort->contextid = context_system::instance()->id;
+        $cohort->name = null;
+        $cohort->idnumber = 'testid';
+        $cohort->description = 'test cohort desc';
+        $cohort->descriptionformat = FORMAT_HTML;
+
+        $this->setExpectedException('coding_exception', 'Missing cohort name in cohort_add_cohort().');
+        cohort_add_cohort($cohort);
+    }
+
+    public function test_cohort_add_cohort_event() {
+        $this->resetAfterTest();
+
+        // Setup cohort data structure.
+        $cohort = new stdClass();
+        $cohort->contextid = context_system::instance()->id;
+        $cohort->name = 'test cohort';
+        $cohort->idnumber = 'testid';
+        $cohort->description = 'test cohort desc';
+        $cohort->descriptionformat = FORMAT_HTML;
 
-        try {
-            $cohort = new stdClass();
-            $cohort->contextid = context_system::instance()->id;
-            $cohort->name = null;
-            $cohort->idnumber = 'testid';
-            $cohort->description = 'test cohort desc';
-            $cohort->descriptionformat = FORMAT_HTML;
-            cohort_add_cohort($cohort);
-
-            $this->fail('Exception expected when trying to add cohort without name');
-        } catch (Exception $e) {
-            $this->assertInstanceOf('coding_exception', $e);
-        }
+        // Catch Events.
+        $sink = $this->redirectEvents();
+
+        // Perform the add operation.
+        $id = cohort_add_cohort($cohort);
+
+        // Capture the event.
+        $events = $sink->get_events();
+        $sink->close();
+
+        // Validate the event.
+        $this->assertCount(1, $events);
+        $event = $events[0];
+        $this->assertInstanceOf('\core\event\cohort_created', $event);
+        $this->assertEquals('cohort', $event->objecttable);
+        $this->assertEquals($id, $event->objectid);
+        $this->assertEquals($cohort->contextid, $event->contextid);
+        $this->assertEquals($cohort, $event->get_record_snapshot('cohort', $id));
+        $this->assertEventLegacyData($cohort, $event);
     }
 
     public function test_cohort_update_cohort() {
@@ -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;
 
index 71bafca..26c3de2 100644 (file)
         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);
index c75e3dc..932a230 100644 (file)
@@ -40,7 +40,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 +51,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");
index fe69315..bfc5641 100644 (file)
@@ -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);
index 31f539c..e3f02be 100644 (file)
@@ -2026,14 +2026,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 +2042,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 +2245,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 +2320,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 +2344,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 +2414,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
@@ -2977,6 +3004,9 @@ function include_course_ajax($course, $usedmodules = array(), $enabledmodules =
             'markedthistopic',
             'move',
             'movesection',
+            'movecontent',
+            'tocontent',
+            'emptydragdropregion'
         ), 'moodle');
 
     // Include format-specific strings
index 0c1ec26..97f7d2a 100644 (file)
@@ -1,5 +1,5 @@
 <?php
-// Allows a teacher/admin to login as another user (in stealth mode)
+// Allows a teacher/admin to login as another user (in stealth mode).
 
 require_once('../config.php');
 require_once('lib.php');
@@ -10,7 +10,7 @@ $redirect = optional_param('redirect', 0, PARAM_BOOL);
 $url = new moodle_url('/course/loginas.php', array('id'=>$id));
 $PAGE->set_url($url);
 
-/// Reset user back to their real self if needed, for security reasons you need to log out and log in again
+// Reset user back to their real self if needed, for security reasons you need to log out and log in again.
 if (session_is_loggedinas()) {
     require_sesskey();
     require_logout();
@@ -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&amp;user=$userid", "$oldfullname -> $newfullname");
-
 $strloginas    = get_string('loginas');
 $strloggedinas = get_string('loggedinas', '', $newfullname);
 
index d4a9054..4888c0a 100644 (file)
@@ -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();
     }
 }
 
index b805289..e780e19 100644 (file)
@@ -1334,4 +1334,368 @@ 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());
+        $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());
+        $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);
+    }
 }
index 0145f0f..fc5f88d 100644 (file)
@@ -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,
index e43b12c..c1bff98 100644 (file)
@@ -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);
index 423ea14..af37580 100644 (file)
@@ -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;}
index 9080888..6c82cc3 100644 (file)
@@ -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;
index 35b22bf..5a15eae 100644 (file)
@@ -81,6 +81,7 @@
 .gradingform_rubric .plainvalue.empty {font-style: italic; color: #AAA;}
 
 .gradingform_rubric.editor .criterion .levels .level .delete {position:absolute;right:0;}
+.dir-rtl .gradingform_rubric.editor .criterion .levels .level .delete {position: relative;}
 .gradingform_rubric .criterion .levels .level .score {font-style:italic;color:#575;font-weight: bold;margin-top:5px;white-space:nowrap;}
 .gradingform_rubric .criterion .levels .level .score .scorevalue {padding-right:5px;}
 
index 7b5fa23..899ebd3 100644 (file)
@@ -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');
index 8533af9..ac23770 100644 (file)
@@ -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.';
index ec04a5c..44e6599 100644 (file)
@@ -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';
index fdbabdb..2eaabc8 100644 (file)
@@ -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<br /><b>{$a->email}</b>.<br />
 The new password was automatically generated - you might like to
 <a href="{$a->link}">change your password</a> to something easier to remember.';
+$string['emptydragdropregion'] = 'empty region';
 $string['enable'] = 'Enable';
 $string['encryptedcode'] = 'Encrypted code';
 $string['english'] = 'English';
@@ -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';
index 2927638..386588a 100644 (file)
@@ -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);
         }
     }
index 25ae3d0..5d209bd 100644 (file)
@@ -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);
index c8e18af..4901456 100644 (file)
@@ -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);
 }
index 3f68221..0fa7c30 100644 (file)
@@ -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;
         }
index 5fcd404..f520fdd 100644 (file)
@@ -108,7 +108,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 +178,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 +187,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");
@@ -395,7 +395,7 @@ 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');
@@ -407,38 +407,38 @@ abstract class base implements \IteratorAggregate {
             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');
+                debugging('Event property level must be a number', 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 +521,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 +529,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/cohort_created.php b/lib/classes/event/cohort_created.php
new file mode 100644 (file)
index 0000000..2ea7ad2
--- /dev/null
@@ -0,0 +1,93 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Cohort updated event.
+ *
+ * @package    core
+ * @copyright  2013 Dan Poltawski <dan@moodle.com>
+ * @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 <dan@moodle.com>
+ * @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';
+        // TODO MDL-41040.
+        $this->data['level'] = 50;
+        $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 (file)
index 0000000..f2d1f32
--- /dev/null
@@ -0,0 +1,93 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Cohort deleted event.
+ *
+ * @package    core
+ * @copyright  2013 Dan Poltawski <dan@moodle.com>
+ * @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 <dan@moodle.com>
+ * @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';
+        // TODO MDL-41040.
+        $this->data['level'] = 50;
+        $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 (file)
index 0000000..95efb88
--- /dev/null
@@ -0,0 +1,96 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * User added to a cohort event.
+ *
+ * @package    core
+ * @copyright  2013 Dan Poltawski <dan@moodle.com>
+ * @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 <dan@moodle.com>
+ * @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';
+        // TODO MDL-41040.
+        $this->data['level'] = 50;
+        $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 (file)
index 0000000..e452715
--- /dev/null
@@ -0,0 +1,97 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * User removed from a cohort event.
+ *
+ * @package    core
+ * @copyright  2013 Dan Poltawski <dan@moodle.com>
+ * @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 <dan@moodle.com>
+ * @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';
+        // TODO MDL-41040.
+        $this->data['level'] = 50;
+        $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 (file)
index 0000000..b36bc45
--- /dev/null
@@ -0,0 +1,93 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Cohort updated event.
+ *
+ * @package    core
+ * @copyright  2013 Dan Poltawski <dan@moodle.com>
+ * @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 <dan@moodle.com>
+ * @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';
+        // TODO MDL-41040.
+        $this->data['level'] = 50;
+        $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 (file)
index 0000000..1f8fde9
--- /dev/null
@@ -0,0 +1,95 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+namespace core\event;
+
+/**
+ * category deleted event.
+ *
+ * @package    core
+ * @copyright  2013 Mark Nelson <markn@moodle.com>
+ * @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'] = 50;
+    }
+
+    /**
+     * 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_content_deleted.php b/lib/classes/event/course_content_deleted.php
new file mode 100644 (file)
index 0000000..4587583
--- /dev/null
@@ -0,0 +1,76 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+namespace core\event;
+
+/**
+ * Course content_deleted event.
+ *
+ * @package    core
+ * @copyright  2013 Mark Nelson <markn@moodle.com>
+ * @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'] = 50;
+    }
+
+    /**
+     * 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 (file)
index 0000000..73c4599
--- /dev/null
@@ -0,0 +1,90 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+namespace core\event;
+
+/**
+ * Course created event.
+ *
+ * @package    core
+ * @copyright  2013 Mark Nelson <markn@moodle.com>
+ * @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'] = 50;
+    }
+
+    /**
+     * 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 (file)
index 0000000..7bdc477
--- /dev/null
@@ -0,0 +1,84 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+namespace core\event;
+
+/**
+ * Course deleted event.
+ *
+ * @package    core
+ * @copyright  2013 Mark Nelson <markn@moodle.com>
+ * @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'] = 50;
+    }
+
+    /**
+     * 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_restored.php b/lib/classes/event/course_restored.php
new file mode 100644 (file)
index 0000000..083ff89
--- /dev/null
@@ -0,0 +1,89 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+namespace core\event;
+
+/**
+ * Course restored event.
+ *
+ * @package    core
+ * @copyright  2013 Mark Nelson <markn@moodle.com>
+ * @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'] = 50;
+    }
+
+    /**
+     * 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 (file)
index 0000000..832c960
--- /dev/null
@@ -0,0 +1,86 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Course section updated.
+ *
+ * @package    core
+ * @copyright  2013 Rajesh Taneja <rajesh@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Course section updated.
+ *
+ * @package    core
+ * @copyright  2013 Rajesh Taneja <rajesh@moodle.com>
+ * @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';
+        // TODO MDL-41040 set level.
+        $this->data['level'] = 50;
+    }
+
+    /**
+     * 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 (file)
index 0000000..3020cc4
--- /dev/null
@@ -0,0 +1,102 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+namespace core\event;
+
+/**
+ * Course updated event.
+ *
+ * @package    core
+ * @copyright  2013 Mark Nelson <markn@moodle.com>
+ * @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'] = 50;
+    }
+
+    /**
+     * 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/user_loggedinas.php b/lib/classes/event/user_loggedinas.php
new file mode 100644 (file)
index 0000000..017a6ae
--- /dev/null
@@ -0,0 +1,86 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * User loggedinas event.
+ *
+ * @package    core
+ * @copyright  2013 Rajesh Taneja <rajesh@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * User loggedinas event class.
+ *
+ * @package    core
+ * @copyright  2013 Rajesh Taneja <rajesh@moodle.com>
+ * @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';
+        // TODO MDL-41040 set level.
+        $this->data['level'] = 50;
+        $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 . '&amp;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));
+    }
+}
index 816c001..0a51649 100644 (file)
@@ -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');
 
index 0bc2f0c..41c7bb2 100644 (file)
@@ -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);
index 2191b89..4000e8e 100644 (file)
@@ -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})";
     }
 
    /**
index cc7b3bb..58874bd 100644 (file)
@@ -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;
index b5062b1..5761c7b 100644 (file)
@@ -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})";
     }
 
     /**
index c01fedb..07ee27e 100644 (file)
@@ -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() {
index ed33fa4..de30300 100644 (file)
@@ -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();
index 790d42e..1d69222 100644 (file)
@@ -26,7 +26,7 @@ define('CLI_SCRIPT', true);
 
 require __DIR__ . '/../../../../config.php';
 
-if (!debugging('', DEBUG_DEVELOPER)) {
+if (!$CFG->debugdeveloper) {
     die('Only for developers!!!!!');
 }
 
index eae4a60..838f845 100644 (file)
@@ -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'));
index 9eeca4a..3783eee 100644 (file)
@@ -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;
             }
         }
index b4c23ac..abeb09d 100644 (file)
@@ -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,12 @@ 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");
+        errorSpan.parentNode.insertBefore(linebreak, errorSpan.nextSibling);
     }
 
     return false;
index 461f552..f60605b 100644 (file)
@@ -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 = '';
index bf8fc20..0750f22 100644 (file)
@@ -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);
index 990fc6a..db40d87 100644 (file)
@@ -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) {
index cfab5d8..3a42fdd 100644 (file)
@@ -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.
index 08da9d8..be02e35 100644 (file)
@@ -1678,7 +1678,8 @@ class xhtml_container_stack {
      * Constructor
      */
     public function __construct() {
-        $this->isdebugging = debugging('', DEBUG_DEVELOPER);
+        global $CFG;
+        $this->isdebugging = $CFG->debugdeveloper;
     }
 
     /**
index be2dfea..d61279a 100644 (file)
@@ -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", '<br />', $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');
             }
index 4374c77..baabc5c 100644 (file)
@@ -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();
             }
index 6dee621..749bcfa 100644 (file)
@@ -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');
index 0a4848d..1bd0d7d 100644 (file)
@@ -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();
index 552e78a..4b9d7a0 100644 (file)
@@ -594,6 +594,7 @@ class phpunit_util extends testing_util {
      */
     public static function reset_debugging() {
         self::$debuggings = array();
+        set_debugging(DEBUG_DEVELOPER);
     }
 
     /**
index 61f5a30..f6c0719 100644 (file)
@@ -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() {
index be19e44..8063106 100644 (file)
@@ -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();
 }
 
 /**
index acc466a..f0ba4a5 100644 (file)
@@ -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 and $CFG->debug & 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,20 +586,40 @@ 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);
+// Load up any configuration from the config table or MUC cache.
+if (PHPUNIT_TEST) {
+    phpunit_util::initialise_cfg();
 } else {
-    $originalconfigdebug = null;
+    initialise_cfg();
 }
 
-// Load up any configuration from the config table
+if (isset($CFG->debug)) {
+    $CFG->debug = (int)$CFG->debug;
+    error_reporting($CFG->debug);
+}  else {
+    $CFG->debug = 0;
+}
+$CFG->debugdeveloper = ($CFG->debug & DEBUG_DEVELOPER);
 
-if (PHPUNIT_TEST) {
-    phpunit_util::initialise_cfg();
+// 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 {
-    initialise_cfg();
+    // 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
@@ -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');
index f75a8a9..1b43764 100644 (file)
@@ -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;
     }
 }
 
index 98fcca0..769cacb 100644 (file)
@@ -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;
 
 
index 414d105..2c06a43 100644 (file)
@@ -637,10 +637,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/sessionlib_test.php b/lib/tests/sessionlib_test.php
new file mode 100644 (file)
index 0000000..5f3b032
--- /dev/null
@@ -0,0 +1,88 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Unit tests for (some of) ../sessionlib.php.
+ *
+ * @package    core_session
+ * @category   phpunit
+ * @copyright  2103 Rajesh Taneja <rajesh@moodle.com>
+ * @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 <rajesh@moodle.com>
+ * @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&amp;user=$user->id", "$oldfullname -> $newfullname");
+        $this->assertEventLegacyLogData($expectedlogdata, $event);
+    }
+}
index fe9728c..50fd289 100644 (file)
@@ -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();
     }
 
     /**
index f7fc350..29a297f 100644 (file)
@@ -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
index e49b956..3edf94d 100644 (file)
@@ -1148,7 +1148,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);
 }
index ee1d160..2b6c7d7 100644 (file)
@@ -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);
+
+    if ($debugdisplay !== null) {
+        $CFG->debugdisplay = (bool)$debugdisplay;
+    }
+}
+
 /**
  * Standard Debugging Function
  *
index ea694d0..491c6b4 100644 (file)
Binary files a/lib/yui/build/moodle-core-blocks/moodle-core-blocks-debug.js and b/lib/yui/build/moodle-core-blocks/moodle-core-blocks-debug.js differ
index 31b92ae..807d9c0 100644 (file)
Binary files a/lib/yui/build/moodle-core-blocks/moodle-core-blocks-min.js and b/lib/yui/build/moodle-core-blocks/moodle-core-blocks-min.js differ
index bc3bb52..274db52 100644 (file)
Binary files a/lib/yui/build/moodle-core-blocks/moodle-core-blocks.js and b/lib/yui/build/moodle-core-blocks/moodle-core-blocks.js differ
index 13cf88e..60490ea 100644 (file)
Binary files a/lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue-debug.js and b/lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue-debug.js differ
index 6e81e7e..17172da 100644 (file)
Binary files a/lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue-min.js and b/lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue-min.js differ
index 13cf88e..60490ea 100644 (file)
Binary files a/lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue.js and b/lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue.js differ
index b8904bb..46f251b 100644 (file)
@@ -2,7 +2,8 @@ YUI.add('moodle-core-dragdrop', function(Y) {
     var MOVEICON = {
         pix: "i/move_2d",
         largepix: "i/dragdrop",
-        component: 'moodle'
+        component: 'moodle',
+        cssclass: 'moodle-core-dragdrop-draghandle'
     };
 
    /*
@@ -33,6 +34,8 @@ YUI.add('moodle-core-dragdrop', function(Y) {
             Y.DD.DDM.on('drop:hit', this.global_drop_hit, this);
             // Listen for all drop:miss events
             Y.DD.DDM.on('drag:dropmiss', this.global_drag_dropmiss, this);
+
+            Y.on('key', this.global_keydown, window, 'down:32,enter,esc', this);
         },
 
         get_drag_handle: function(title, classname, iconclass, large) {
@@ -53,17 +56,19 @@ YUI.add('moodle-core-dragdrop', function(Y) {
             var dragelement = Y.Node.create('<span></span>')
                 .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('<ul></ul>');
+            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('<li></li>');
+                    listlink = Y.Node.create('<a></a>');
+                    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']});
index 4597acc..f3d17d2 100644 (file)
@@ -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
+};
index cd489a1..28d4729 100644 (file)
@@ -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();
         }
index 3aecc7f..c33fd3f 100644 (file)
@@ -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; $j<count($columns->columns); $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;
                     }
index 13646ed..d6c1cff 100644 (file)
@@ -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;
 }
index ff309cc..9f2d6c1 100644 (file)
@@ -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;
index a4311a8..4a29a74 100644 (file)
@@ -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;
                 }
index 8916805..b8f2c79 100644 (file)
@@ -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);
index ed6a085..0d62887 100644 (file)
@@ -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())) {
index f6ef2bc..39740fd 100644 (file)
@@ -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);
index 600ee88..93011aa 100644 (file)
@@ -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);
+    }
+}
index 47dc925..a75cbd4 100644 (file)
@@ -1379,6 +1379,35 @@ 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() {
+
+        // Value will be either a plain MD5 hash, or some real content, followed
+        // by an MD5 hash in a HTML comment. We only want the value in the latter case.
+        if (preg_match('/\s*<!-- File hash: [0-9a-zA-Z]{32} -->\s*$/', $this->value)) {
+            $value = preg_replace('/\s*<!-- File hash: [0-9a-zA-Z]{32} -->\s*$/', '', $this->value);
+
+        } else if (preg_match('/^[0-9a-zA-Z]{32}$/', $this->value)) {
+            $value = null;
+
+        } else {
+            throw new coding_exception('$value passed to question_file_loader::get_question_file_saver' .
+                    ' was not of the expected form.');
+        }
+
+        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);
+    }
 }
 
 
index eea1069..e3300c3 100644 (file)
@@ -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;
         }
index 90ee1d5..892c738 100644 (file)
@@ -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;
index 29a4fc7..a9b7cc2 100644 (file)
@@ -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 = '';
         }
index 02e7d74..fd89b87 100644 (file)
@@ -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
index 856619b..bd9cdb7 100644 (file)
@@ -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 <img src="http://example.com/globe.png" alt="world" />';
@@ -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() {
index 39ad956..fbc7a1a 100644 (file)
@@ -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,126 @@ 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&amp;.*?itemid=(\d+)&amp;/', $this->currentoutput, $matches)) {
+            throw new coding_exception('Editor draft item id not found.');
+        }
+        $editordraftid = $matches[1];
+        if (!preg_match('/env=filemanager&amp;action=browse&amp;.*?itemid=(\d+)&amp;/', $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: <img src="' . $CFG->wwwroot .
+                                "/draftfile.php/{$usercontextid}/user/draft/{$editordraftid}/smile.txt" .
+                                '" alt="smile">.',
+                '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&amp;.*?itemid=(\d+)&amp;/', $this->currentoutput, $matches)) {
+            throw new coding_exception('Editor draft item id not found.');
+        }
+        $editordraftid = $matches[1];
+        if (!preg_match('/env=filemanager&amp;action=browse&amp;.*?itemid=(\d+)&amp;/', $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: <img src="' . $CFG->wwwroot .
+                                "/draftfile.php/{$usercontextid}/user/draft/{$editordraftid}/smile.txt" .
+                                '" alt="smile">.',
+                '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&amp;.*?itemid=(\d+)&amp;/', $this->currentoutput, $matches)) {
+            throw new coding_exception('Editor draft item id not found.');
+        }
+        $editordraftid = $matches[1];
+        if (!preg_match('/env=filemanager&amp;action=browse&amp;.*?itemid=(\d+)&amp;/', $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: <img src="' . $CFG->wwwroot .
+                                "/draftfile.php/{$usercontextid}/user/draft/{$editordraftid}/smile.txt" .
+                                '" alt="smile">.',
+                '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);
+    }
 }
index ff85c2a..42bfd04 100644 (file)
@@ -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.
index 2fbe4e0..80cfde6 100644 (file)
@@ -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();
 
index aeeaccf..3916310 100644 (file)
@@ -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;
index 2554177..e99cdd6 100644 (file)
@@ -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));
 
index c6b4061..9ef422e 100644 (file)
@@ -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;
index b14a127..9930546 100644 (file)
@@ -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 {
index cbfeaef..9b65813 100644 (file)
@@ -97,66 +97,24 @@ echo $OUTPUT->heading($title, 2, 'centre');
 
 // Prepare data for tags
 $courselink = '';
-if ($courseid) { $courselink = '&amp;courseid='.$courseid; }
+if ($courseid) {
+    $courselink = '&amp;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 = '
-    <div class="coursetag_more_title">
-        <div style="padding-bottom:5px">'.$welcome.'</div>
-        <div class="coursetag_more_link">'.$link1.'</div>
-        <div class="coursetag_more_link">'.$link2.'</div>
-    </div>
-    <div class="coursetag_more_tags '.$fclass.'">'.
-        $tags.'
-    </div>';
+<div class="coursetag_more_title">
+<div style="padding-bottom:5px">'.$welcome.'</div>
+<div class="coursetag_more_link">'.$link1.'</div>
+<div class="coursetag_more_link">'.$link2.'</div>
+</div>
+<div class="coursetag_more_tags '.$fclass.'">'.
+$tags.'
+</div>';
 echo $outstr;
 
 echo $OUTPUT->footer();
index f281418..53d4ff0 100644 (file)
@@ -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
  *
index db00484..55cd31e 100644 (file)
@@ -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<ul class='tag_cloud inline-list'>\n";
     foreach ($etags as $tag) {
index 5f27184..e8366c8 100644 (file)
@@ -1,6 +1,14 @@
 This files describes API changes in tagging, information provided
 here is intended especially for developers.
 
+=== 2.6 ===
+
+More cleanup was done to tag cloud sorting which involved some API changes, see MDL_39800
+* tag_print_cloud() arguments were changed.
+* coursetag_get_tags() arguments were changed.
+* coursetag_get_all_tags() arguments were changed.
+* coursetag_sort() was removed.
+
 === 2.4 ===
 
 Significant cleanup was done to course tags which involved some API
index eba94e6..dc194b2 100644 (file)
@@ -8,6 +8,7 @@
 
 .path-admin #assignrole {width: 60%;margin-left: auto;margin-right: auto;}
 .path-admin .admintable .leftalign {text-align: left;}
+.dir-rtl.path-admin .admintable .leftalign {text-align: right;}
 .path-admin .admintable .centeralign {text-align: center;}
 
 .path-admin .admintable.environmenttable .name,
index ed9cb64..d471aa1 100644 (file)
@@ -263,6 +263,7 @@ a.skip:active {position: static;display: block;}
 .mform .fitem fieldset.felement {margin-left:15%;padding-left:1%;margin-bottom:0}
 .mform .error,
 .mform .required {color:#A00;}
+.mform span.error {display: inline-block;padding: 4px;margin-bottom: 4px;background-color: #F2DEDE;border: 1px solid #EED3D7;}
 .mform .required .fgroup span label {color:#000;}
 .mform .fdescription.required {color:#A00;text-align:right;}
 .dir-rtl .mform .fdescription.required {text-align:left;}
@@ -1470,3 +1471,6 @@ div.badge .expireimage { width: 100px; height: 100px; left: 20px; top: 0px; }
 .dir-rtl .menu.align-tr-tr {right: auto;left: 0;}
 .dir-rtl .menu.align-bl-tr {right: 100%;left: auto;}
 .dir-rtl .menu.align-br-tr {right: auto;left: 0;}
+
+ul.dragdrop-keyboard-drag li { list-style-type: none; }
+.block-control-actions .moodle-core-dragdrop-draghandle img { width: 12px; height: 12px; }
index 6bc054d..4c34a44 100644 (file)
@@ -1064,6 +1064,9 @@ body.tag .managelink {
 #page-enrol-users .enrol_user_buttons {
     float: right;
 }
+#page-enrol-users.dir-rtl .enrol_user_buttons {
+    float: left;
+}
 #page-enrol-users .enrol_user_buttons .enrolusersbutton {
     margin-left: 1em;
     display: inline;
@@ -2095,3 +2098,12 @@ div.badge .expireimage {
         text-align: right;
     }
 }
+
+ul.dragdrop-keyboard-drag li {
+    list-style-type: none;
+}
+
+.block-control-actions .moodle-core-dragdrop-draghandle img {
+    width: 12px;
+    height: 12px;
+}
index 118fdda..76246c2 100644 (file)
@@ -23,6 +23,14 @@ form {
 .mform fieldset.error {
     border: 1px solid @errorText;
 }
+.mform span.error {
+    display: inline-block;
+    border: 1px solid @errorBorder;
+    border-radius: 4px;
+    background-color: @errorBackground;
+    padding: 4px;
+    margin-bottom: 4px;
+}
 .mform fieldset.collapsible legend a.fheader {
     padding: 0 5px 0 20px;
     margin-left: -20px;
index 390c21c..c6e5109 100644 (file)
     margin-left: 0.3em;
     margin-right: 0.3em;
 }
+body.path-question-type .fitem_fgroup .accesshide {
+    font: inherit;
+    left: 0;
+    position: static;
+    padding-right: .3em;
+}
 .que {
     clear: left;
     text-align: left;
index b7671ec..a4334a4 100644 (file)
@@ -1,4 +1,4 @@
-.layout-option-noheader #page-header,.layout-option-nonavbar #page-navbar,.layout-option-nofooter #page-footer,.layout-option-nocourseheader .course-content-header,.layout-option-nocoursefooter .course-content-footer{display:none}.empty-region-side-pre #block-region-side-pre,.empty-region-side-post #block-region-side-post{display:none}.empty-region-side-post #region-bs-main-and-pre.span9{width:100%}.empty-region-side-pre #region-main{float:none;width:100%}.empty-region-side-post.used-region-side-pre #region-main.span8{width:74.46808510638297%;*width:74.41489361702126%}.empty-region-side-post.used-region-side-pre #block-region-side-pre.span4{width:23.404255319148934%;*width:23.351063829787233%}.empty-region-side-post #region-bs-main-and-post.span9 #region-main.span8{width:100%}.dir-ltr,.mdl-left,.dir-rtl .mdl-right{text-align:left}.dir-rtl,.mdl-right,.dir-rtl .mdl-left{text-align:right}#add,#remove,.centerpara,.mdl-align{text-align:center}a.dimmed,a.dimmed:link,a.dimmed:visited,a.dimmed_text,a.dimmed_text:link,a.dimmed_text:visited,.dimmed_text,.dimmed_text a,.dimmed_text a:link,.dimmed_text a:visited,.usersuspended,.usersuspended a,.usersuspended a:link,.usersuspended a:visited,.dimmed_category,.dimmed_category a{color:#999}.activity.label .dimmed_text{opacity:.5;filter:alpha(opacity=50)}.unlist,.unlist li,.inline-list,.inline-list li,.block .list,.block .list li,.section li.activity,.section li.movehere,.tabtree li{padding:0;margin:0;list-style:none}.inline,.inline-list li{display:inline}.notifytiny{font-size:10.5px}.notifytiny li,.notifytiny td{font-size:100%}.red,.notifyproblem{color:#b94a48}.green,.notifysuccess{color:#468847}.reportlink{text-align:right}a.autolink.glossary:hover{cursor:help}.collapsibleregioncaption{white-space:nowrap}.collapsibleregioncaption img{vertical-align:middle}.jsenabled .hiddenifjs{display:none}.visibleifjs{display:none}.jsenabled .visibleifjs{display:inline}.jsenabled .collapsibleregion{overflow:hidden}.jsenabled .collapsed .collapsibleregioninner{visibility:hidden}.yui-overlay .yui-widget-bd{position:relative;top:0;left:0;z-index:1;padding:2px 5px;color:#000;background-color:#ffee69;border:1px solid #a6982b;border-top-color:#d4c237}.clearer{display:block;height:1px;padding:0;margin:0;clear:both;background:transparent;border-width:0}.bold,.warning,.errorbox .title,.pagingbar .title,.pagingbar .thispage,.headingblock{font-weight:bold}img.resize{width:1em;height:1em}.block img.resize,.breadcrumb img.resize{width:.8em;height:.9em}img.icon{width:16px;height:16px;padding-right:6px;vertical-align:text-bottom}.dir-rtl img.icon{padding-right:0;padding-left:6px}img.iconsmall{width:12px;height:12px;margin-right:3px;vertical-align:middle}img.iconhelp,.helplink img{width:16px;height:16px;padding-left:3px;vertical-align:text-bottom}.dir-rtl img.iconhelp,.dir-rtl .helplink img{padding-right:3px;padding-left:0}img.iconlarge{width:24px;height:24px;vertical-align:middle}img.iconsort{padding-left:.3em;margin-bottom:.15em;vertical-align:text-bottom}.dir-rtl img.iconsort{padding-right:.3em;padding-left:0}img.icontoggle{width:50px;height:17px;vertical-align:middle}img.iconkbhelp{width:49px;height:17px}img.icon-pre,.dir-rtl img.icon-post{padding-right:3px;padding-left:0}img.icon-post,.dir-rtl img.icon-pre{padding-right:0;padding-left:3px}.boxaligncenter{margin-right:auto;margin-left:auto}.boxalignright{margin-right:0;margin-left:auto}.boxalignleft{margin-right:auto;margin-left:0}.boxwidthnarrow{width:30%}.boxwidthnormal{width:50%}.boxwidthwide{width:80%}.headermain{font-weight:bold}#maincontent{display:block;height:1px;overflow:hidden}img.uihint{cursor:help}#addmembersform table{margin-right:auto;margin-left:auto}.flexible th{white-space:nowrap}table.flexible .emptyrow{display:none}img.emoticon{width:15px;height:15px;vertical-align:middle}form.popupform,form.popupform div{display:inline}.arrow_button input{overflow:hidden}.action-icon img.smallicon{margin:0 .3em;vertical-align:text-bottom}.main img{vertical-align:middle}.no-overflow{padding-bottom:1px;overflow:auto}.pagelayout-report .no-overflow{overflow:visible}.no-overflow>.generaltable{margin-bottom:0}.accesshide{position:absolute;left:-10000px;font-size:1em;font-weight:normal}.dir-rtl .accesshide{top:-30000px;left:auto}span.hide,div.hide{display:none}a.skip-block,a.skip{position:absolute;top:-1000em;font-size:.85em;text-decoration:none}a.skip-block:focus,a.skip-block:active,a.skip:focus,a.skip:active{position:static;display:block}.skip-block-to{display:block;height:1px;overflow:hidden}.addbloglink{text-align:center}.blog_entry .audience{padding-right:4px;text-align:right}.blog_entry .tags{margin-top:15px}.blog_entry .tags .action-icon img.smallicon{width:16px;height:16px}.blog_entry .content{margin-left:43px}#page-group-index #groupeditform{text-align:center}#doc-contents h1{margin:1em 0 0 0}#doc-contents ul{width:90%;padding:0;margin:0}#doc-contents ul li{list-style-type:none}.groupmanagementtable td{vertical-align:top}.groupmanagementtable #existingcell,.groupmanagementtable #potentialcell{width:42%}.groupmanagementtable #buttonscell{width:16%}.groupmanagementtable #removeselect_wrapper,.groupmanagementtable #addselect_wrapper{width:100%}.groupmanagementtable #removeselect_wrapper label,.groupmanagementtable #addselect_wrapper label{font-weight:normal}.dir-rtl .groupmanagementtable p{text-align:right}#group-usersummary{width:14em}.groupselector{margin-top:3px;margin-bottom:3px}.loginbox{margin:15px;overflow:visible}.loginbox.twocolumns{margin:15px}.loginbox h2,.loginbox .subcontent{padding:10px;margin:5px;text-align:center}.loginbox .loginpanel .desc{padding:0;margin:0;margin-top:15px;margin-bottom:5px}.loginbox .signuppanel .subcontent{text-align:left}.dir-rtl .loginbox .signuppanel .subcontent{text-align:right}.loginbox .loginsub{margin-right:0;margin-left:0}.loginbox .guestsub,.loginbox .forgotsub,.loginbox .potentialidps{margin:5px 12%}.loginbox .potentialidps .potentialidplist{margin-left:40%}.loginbox .potentialidps .potentialidplist div{text-align:left}.loginbox .loginform{margin-top:1em;text-align:left}.loginbox .loginform .form-label{float:left;width:44%;text-align:right;white-space:nowrap;direction:rtl}.dir-rtl .loginbox .loginform .form-label{float:left;width:44%;text-align:right;white-space:nowrap;direction:ltr}.loginbox .loginform .form-input{float:right;width:55%}.loginbox .loginform .form-input input{width:6em}.loginbox .signupform{margin-top:1em;text-align:center}.loginbox.twocolumns .loginpanel,.loginbox.twocolumns .signuppanel{display:block;float:left;width:48%;min-height:30px;padding:0;padding-bottom:2000px;margin:0;margin-bottom:-2000px;margin-left:2.76243%;border:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.loginbox .potentialidp .smallicon{margin:0 .3em;vertical-align:text-bottom}.notepost{margin-bottom:1em}.notepost .userpicture{float:left;margin-right:5px}.notepost .content,.notepost .footer{clear:both}.notesgroup{margin-left:20px}.path-my .coursebox .overview{margin:15px 30px 10px 30px}.path-my .coursebox .info{float:none;margin:0}.mod_introbox{padding:10px}table.mod_index{width:100%}.comment-ctrl{display:none;padding:0;margin:0;font-size:12px}.comment-ctrl h5{padding:5px;margin:0}.comment-area{max-width:400px;padding:5px}.comment-area textarea{width:100%;overflow:auto}.comment-area .fd{text-align:right}.comment-meta span{color:gray}.comment-link img{vertical-align:text-bottom}.comment-list{padding:0;margin:0;overflow:auto;font-size:11px;list-style:none}.comment-list li{position:relative;padding:.3em;margin:2px;margin-bottom:5px;clear:both;list-style:none}.comment-list li.first{display:none}.comment-paging{text-align:center}.comment-paging .pageno{padding:2px}.comment-paging .curpage{border:1px solid #CCC}.comment-message .picture{float:left;width:20px}.dir-rtl .comment-message .picture{float:right}.comment-message .text{padding:0;margin:0}.comment-message .text p{padding:0;margin:0 18px 0 0}.comment-delete{position:absolute;top:0;right:0;margin:.3em}.dir-rtl .comment-delete{position:absolute;right:auto;left:0;margin:.3em}.comment-delete-confirm{width:5em;padding:2px;text-align:center;background:#eee}.comment-container{float:left;margin:4px}.comment-report-selectall{display:none}.comment-link{display:none}.jsenabled .comment-link{display:block}.jsenabled .showcommentsnonjs{display:none}.jsenabled .comment-report-selectall{display:inline}.completion-expired{background:#f2dede}.completion-expected{font-size:10.5px}.completion-sortchoice,.completion-identifyfield{font-size:10.5px;vertical-align:bottom}.completion-progresscell{text-align:right}.completion-expired .completion-expected{font-weight:bold}#page-tag-coursetags_edit .coursetag_edit_centered{position:relative;width:600px;margin:20px auto}#page-tag-coursetags_edit .coursetag_edit_row{clear:both}#page-tag-coursetags_edit .coursetag_edit_row .coursetag_edit_left{float:left;width:50%;text-align:right}#page-tag-coursetags_edit .coursetag_edit_row .coursetag_edit_right{margin-left:50%}#page-tag-coursetags_edit .coursetag_edit_input3{display:none}#page-tag-coursetags_more .coursetag_more_large{font-size:120%}#page-tag-coursetags_more .coursetag_more_small{font-size:80%}#page-tag-coursetags_more .coursetag_more_link{font-size:80%}#tag-description,#tag-blogs{width:100%}#tag-management-box{margin-bottom:10px;line-height:20px}#tag-user-table{width:100%;padding:3px;clear:both}#tag-user-table{*zoom:1}#tag-user-table:before,#tag-user-table:after{display:table;line-height:0;content:""}#tag-user-table:after{clear:both}img.user-image{width:100px;height:100px}#small-tag-cloud-box{width:300px;margin:0 auto}#big-tag-cloud-box{float:none;width:600px;margin:0 auto}ul#tag-cloud-list{padding:5px;margin:0;list-style:none}ul#tag-cloud-list li{display:inline;margin:0;list-style-type:none}#tag-search-box{margin:10px auto;text-align:center}#tag-search-results-container{width:100%;padding:0}#tag-search-results{display:block;float:left;width:60%;padding:0;margin:15px 20% 0 20%}#tag-search-results li{float:left;width:30%;padding-right:1%;padding-left:1%;line-height:20px;text-align:left;list-style:none}span.flagged-tag,span.flagged-tag a{color:#b94a48}table#tag-management-list{width:100%;text-align:left}table#tag-management-list td,table#tag-management-list th{padding:4px;text-align:left;vertical-align:middle}.tag-management-form{text-align:center}#relatedtags-autocomplete-container{width:100%;min-height:4.6em;margin-right:auto;margin-left:auto}#relatedtags-autocomplete{position:relative;display:block;width:60%;margin-right:auto;margin-left:auto}#relatedtags-autocomplete .yui-ac-content{position:absolute;left:20%;z-index:9050;width:420px;overflow:hidden;background:#fff;border:1px solid #404040}#relatedtags-autocomplete .ysearchquery{position:absolute;right:10px;z-index:10;color:#808080}#relatedtags-autocomplete .yui-ac-shadow{position:absolute;z-index:9049;width:100%;margin:.3em;background:#a0a0a0}#relatedtags-autocomplete ul{width:100%;padding:0;margin:0;list-style-type:none}#relatedtags-autocomplete li{padding:0 5px;white-space:nowrap;cursor:default}#relatedtags-autocomplete li.yui-ac-highlight{background:#ffc}h2.tag-heading,div#tag-description,div#tag-blogs,body.tag .managelink{padding:5px}.tag_cloud .s20{font-size:1.5em;font-weight:bold}.tag_cloud .s19{font-size:1.5em}.tag_cloud .s18{font-size:1.4em;font-weight:bold}.tag_cloud .s17{font-size:1.4em}.tag_cloud .s16{font-size:1.3em;font-weight:bold}.tag_cloud .s15{font-size:1.3em}.tag_cloud .s14{font-size:1.2em;font-weight:bold}.tag_cloud .s13{font-size:1.2em}.tag_cloud .s12,.tag_cloud .s11{font-size:1.1em;font-weight:bold}.tag_cloud .s10,.tag_cloud .s9{font-size:1.1em}.tag_cloud .s8,.tag_cloud .s7{font-size:1em;font-weight:bold}.tag_cloud .s6,.tag_cloud .s5{font-size:1em}.tag_cloud .s4,.tag_cloud .s3{font-size:.9em;font-weight:bold}.tag_cloud .s2,.tag_cloud .s1{font-size:.9em}.tag_cloud .s0{font-size:.8em}#webservice-doc-generator td{text-align:left;border:0 solid black}.smartselect{position:absolute}.smartselect .smartselect_mask{background-color:#fff}.smartselect ul{padding:0;margin:0}.smartselect ul li{list-style:none}.smartselect .smartselect_menu{margin-right:5px}.safari .smartselect .smartselect_menu{margin-left:2px}.smartselect .smartselect_menu,.smartselect .smartselect_submenu{display:none;background-color:#FFF;border:1px solid #000}.smartselect .smartselect_menu.visible,.smartselect .smartselect_submenu.visible{display:block}.smartselect .smartselect_menu_content ul li{position:relative;padding:2px 5px}.smartselect .smartselect_menu_content ul li a{color:#333;text-decoration:none}.smartselect .smartselect_menu_content ul li a.selectable{color:inherit}.smartselect .smartselect_submenuitem{background-image:url([[pix:moodle|t/collapsed]]);background-position:100%;background-repeat:no-repeat}.smartselect.spanningmenu .smartselect_submenu{position:absolute;top:-1px;left:100%}.smartselect.spanningmenu .smartselect_submenu a{padding-right:16px;white-space:nowrap}.smartselect.spanningmenu .smartselect_menu_content ul li a.selectable:hover{text-decoration:underline}.smartselect.compactmenu .smartselect_submenu{position:relative;z-index:1010;display:none;margin:2px -3px;margin-left:10px;border-width:0}.smartselect.compactmenu .smartselect_submenu.visible{display:block}.smartselect.compactmenu .smartselect_menu{z-index:1000;overflow:hidden}.smartselect.compactmenu .smartselect_submenu .smartselect_submenu{z-index:1020}.smartselect.compactmenu .smartselect_submenuitem:hover>.smartselect_menuitem_label{font-weight:bold}#page-admin-registration-register .registration_textfield{width:300px}.userenrolment{width:100%;border-collapse:collapse}.userenrolment td{height:41px;padding:0}.userenrolment .subfield{margin-right:5px}.userenrolment .col_userdetails .subfield_picture{float:left}.userenrolment .col_lastseen{width:150px}.userenrolment .col_role{width:262px}.userenrolment .col_role .roles{margin-right:30px}.userenrolment .col_role .role{float:left;padding:3px;margin:3px}.dir-rtl .userenrolment .col_role .role{float:right}.userenrolment .col_role .role a{margin-left:3px;cursor:pointer}.userenrolment .col_role .addrole{float:right;width:18px;height:18px;margin:3px;text-align:center;background-color:#dff0d8;border:1px solid #d6e9c6}.userenrolment .col_role .addrole img{vertical-align:baseline}.userenrolment .hasAllRoles .col_role .addrole{display:none}.userenrolment .col_group .groups{margin-right:30px}.userenrolment .col_group .group{float:left;padding:3px;margin:3px;white-space:nowrap}.userenrolment .col_group .group a{margin-left:3px;cursor:pointer}.userenrolment .col_group .addgroup{float:right;width:18px;height:18px;margin:3px;text-align:center}.userenrolment .col_group .addgroup a img{vertical-align:bottom}.userenrolment .col_enrol .enrolment{float:left;padding:3px;margin:3px}.userenrolment .col_enrol .enrolment a{float:right;margin-left:3px}#page-enrol-users .enrol_user_buttons{float:right}#page-enrol-users .enrol_user_buttons .enrolusersbutton{display:inline;margin-left:1em}#page-enrol-users .enrol_user_buttons .enrolusersbutton div,#page-enrol-users .enrol_user_buttons .enrolusersbutton form{display:inline}#page-enrol-users .enrol_user_buttons .enrolusersbutton input{padding-right:6px;padding-left:6px}#page-enrol-users.dir-rtl .col_userdetails .subfield_picture{float:right}#page-enrol-users .user-enroller-panel .uep-search-results .user .details{width:237px}.dir-rtl .headermain{float:right}.dir-rtl .headermenu{float:left}.dir-rtl .loginbox .loginform .form-label{float:right;text-align:left}.dir-rtl .loginbox .loginform .form-input{text-align:right}.dir-rtl .yui3-menu-hidden{left:0}#page-admin-roles-define.dir-rtl #rolesform .felement{margin-right:180px}#page-message-edit.dir-rtl table.generaltable th.c0{text-align:right}.corelightbox{position:absolute;top:0;left:0;width:100%;height:100%;text-align:center;background-color:#CCC}.corelightbox img{position:fixed;top:50%;left:50%}.mod-indent-1{margin-left:30px}.mod-indent-2{margin-left:60px}.mod-indent-3{margin-left:90px}.mod-indent-4{margin-left:120px}.mod-indent-5{margin-left:150px}.mod-indent-6{margin-left:180px}.mod-indent-7{margin-left:210px}.mod-indent-8{margin-left:240px}.mod-indent-9{margin-left:270px}.mod-indent-10{margin-left:300px}.mod-indent-11{margin-left:330px}.mod-indent-12{margin-left:360px}.mod-indent-13{margin-left:390px}.mod-indent-14{margin-left:420px}.mod-indent-15,.mod-indent-huge{margin-left:420px}.dir-rtl .mod-indent-1{margin-right:30px;margin-left:0}.dir-rtl .mod-indent-2{margin-right:60px;margin-left:0}.dir-rtl .mod-indent-3{margin-right:90px;margin-left:0}.dir-rtl .mod-indent-4{margin-right:120px;margin-left:0}.dir-rtl .mod-indent-5{margin-right:150px;margin-left:0}.dir-rtl .mod-indent-6{margin-right:180px;margin-left:0}.dir-rtl .mod-indent-7{margin-right:210px;margin-left:0}.dir-rtl .mod-indent-8{margin-right:240px;margin-left:0}.dir-rtl .mod-indent-9{margin-right:270px;margin-left:0}.dir-rtl .mod-indent-10{margin-right:300px;margin-left:0}.dir-rtl .mod-indent-11{margin-right:330px;margin-left:0}.dir-rtl .mod-indent-12{margin-right:360px;margin-left:0}.dir-rtl .mod-indent-13{margin-right:390px;margin-left:0}.dir-rtl .mod-indent-14{margin-right:420px;margin-left:0}.dir-rtl .mod-indent-15,.dir-rtl .mod-indent-huge{margin-right:420px;margin-left:0}.resourcecontent .mediaplugin_mp3 object{width:600px;height:25px}.resourcecontent audio.mediaplugin_html5audio{width:600px}.resourceimage{max-width:100%}.mediaplugin_mp3 object{width:300px;height:15px}audio.mediaplugin_html5audio{width:300px}.core_media_preview.pagelayout-embedded #content{padding:0}.core_media_preview.pagelayout-embedded #maincontent{height:0}.core_media_preview.pagelayout-embedded .mediaplugin{margin:0}.dir-rtl .ygtvtn,.dir-rtl .ygtvtm,.dir-rtl .ygtvtmh,.dir-rtl .ygtvtmhh,.dir-rtl .ygtvtp,.dir-rtl .ygtvtph,.dir-rtl .ygtvtphh,.dir-rtl .ygtvln,.dir-rtl .ygtvlm,.dir-rtl .ygtvlmh,.dir-rtl .ygtvlmhh,.dir-rtl .ygtvlp,.dir-rtl .ygtvlph,.dir-rtl .ygtvlphh,.dir-rtl .ygtvdepthcell,.dir-rtl .ygtvok,.dir-rtl .ygtvok:hover,.dir-rtl .ygtvcancel,.dir-rtl .ygtvcancel:hover{width:18px;height:22px;cursor:pointer;background-image:url([[pix:theme|yui2-treeview-sprite-rtl]]);background-repeat:no-repeat}.dir-rtl .ygtvtn{background-position:0 -5600px}.dir-rtl .ygtvtm{background-position:0 -4000px}.dir-rtl .ygtvtmh,.dir-rtl .ygtvtmhh{background-position:0 -4800px}.dir-rtl .ygtvtp{background-position:0 -6400px}.dir-rtl .ygtvtph,.dir-rtl .ygtvtphh{background-position:0 -7200px}.dir-rtl .ygtvln{background-position:0 -1600px}.dir-rtl .ygtvlm{background-position:0 0}.dir-rtl .ygtvlmh,.dir-rtl .ygtvlmhh{background-position:0 -800px}.dir-rtl .ygtvlp{background-position:0 -2400px}.dir-rtl .ygtvlph,.dir-rtl .ygtvlphh{background-position:0 -3200px}.dir-rtl .ygtvdepthcell{background-position:0 -8000px}.dir-rtl .ygtvok{background-position:0 -8800px}.dir-rtl .ygtvok:hover{background-position:0 -8844px}.dir-rtl .ygtvcancel{background-position:0 -8822px}.dir-rtl .ygtvcancel:hover{background-position:0 -8866px}.dir-rtl.yui-skin-sam .yui-panel .hd{text-align:right}.dir-rtl .yui-skin-sam .yui-layout .yui-layout-unit div.yui-layout-bd{text-align:right}.dir-rtl .clearlooks2.ie9 .mceAlert .mceMiddle span,.dir-rtl .clearlooks2 .mceConfirm .mceMiddle span{top:44px}.dir-rtl .o2k7Skin table,.dir-rtl .o2k7Skin tbody,.dir-rtl .o2k7Skin a,.dir-rtl .o2k7Skin img,.dir-rtl .o2k7Skin tr,.dir-rtl .o2k7Skin div,.dir-rtl .o2k7Skin td,.dir-rtl .o2k7Skin iframe,.dir-rtl .o2k7Skin span,.dir-rtl .o2k7Skin *,.dir-rtl .o2k7Skin .mceText,.dir-rtl .o2k7Skin .mceListBox .mceText{text-align:right}.path-rating .ratingtable{width:100%;margin-bottom:1em}.path-rating .ratingtable th.rating{width:100%}.path-rating .ratingtable td.rating,.path-rating .ratingtable td.time{text-align:center;white-space:nowrap}.initialbar a{padding-right:2px}.moodle-dialogue-base .moodle-dialogue-lightbox{background-color:#AAA}.moodle-dialogue-base .hidden,.moodle-dialogue-base .moodle-dialogue-hidden{display:none}.no-scrolling{overflow:hidden}.moodle-dialogue-fullscreen{top:0;left:0;width:100%;height:100%;overflow:auto}.moodle-dialogue-base .moodle-dialogue{z-index:600;padding:0;margin:0;background:0;border:0}.moodle-dialogue-base .moodle-dialogue-wrap{margin-top:-3px;margin-left:-3px;background-color:#fff;border:1px solid #ccc;-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px;-webkit-box-shadow:5px 5px 20px 0 #666;-moz-box-shadow:5px 5px 20px 0 #666;box-shadow:5px 5px 20px 0 #666}.moodle-dialogue-base .moodle-dialogue-wrap .moodle-dialogue-hd{padding:5px;margin:0;font-size:12px;font-weight:normal;letter-spacing:1px;color:#333;text-align:center;text-shadow:1px 1px 1px #fff;background:#ccc;background-color:#ebebeb;background-image:-moz-linear-gradient(top,#fff,#ccc);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#ccc));background-image:-webkit-linear-gradient(top,#fff,#ccc);background-image:-o-linear-gradient(top,#fff,#ccc);background-image:linear-gradient(to bottom,#fff,#ccc);background-repeat:repeat-x;border-bottom:1px solid #bbb;-webkit-border-radius:10px 10px 0 0;-moz-border-radius:10px 10px 0 0;border-radius:10px 10px 0 0;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffcccccc',GradientType=0);filter:dropshadow(color=#ffffff,offx=1,offy=1)}.moodle-dialogue-base .moodle-dialogue-wrap .moodle-dialogue-hd h1{display:inline;padding:0;margin:0;font-size:100%;font-weight:bold}.moodle-dialogue-base .moodle-dialogue-wrap .moodle-dialogue-hd .yui3-widget-buttons{padding:5px}.moodle-dialogue-base .closebutton{display:inline-block;float:right;width:25px;height:15px;padding:0;vertical-align:middle;cursor:pointer;background-image:url([[pix:theme|sprite]]);background-repeat:no-repeat;border-style:none}.dir-rtl .moodle-dialogue-base .moodle-dialogue-wrap .moodle-dialogue-hd .yui3-widget-buttons{right:auto;left:0}.moodle-dialogue-base .moodle-dialogue .moodle-dialogue-bd{padding:1em;overflow:auto;font-size:12px;line-height:2em;color:#555}.moodle-dialogue-base .moodle-dialogue-wrap .moodle-dialogue-content{padding:0;background:#FFF}.moodle-dialogue-base .moodle-dialogue-fullscreen .moodle-dialogue-hd{padding:10px;font-size:16px}.moodle-dialogue-base .moodle-dialogue-fullscreen,.moodle-dialogue-fullscreen .moodle-dialogue-content{width:100%;height:100%;margin:0;border:0}.moodle-dialogue-base .moodle-dialogue-fullscreen .moodle-dialogue-hd,.moodle-dialogue-base .moodle-dialogue-fullscreen .moodle-dialogue-wrap{border-radius:0}.moodle-dialogue-confirm .confirmation-dialogue{text-align:center}.moodle-dialogue-confirm .confirmation-dialogue input{text-align:center}.moodle-dialogue-exception .moodle-exception-message{text-align:center}.moodle-dialogue-exception .moodle-exception-param label{font-weight:bold}.moodle-dialogue-exception .param-stacktrace label{background-color:#EEE;border:1px solid #ccc;border-bottom-width:0}.moodle-dialogue-exception .param-stacktrace pre{background-color:#fff;border:1px solid #ccc}.moodle-dialogue-exception .param-stacktrace .stacktrace-file{font-size:11.9px;color:navy}.moodle-dialogue-exception .param-stacktrace .stacktrace-line{font-size:11.9px;color:#b94a48}.moodle-dialogue-exception .param-stacktrace .stacktrace-call{font-size:90%;color:#333;border-bottom:1px solid #eee}.moodle-dialogue-base .moodle-dialogue .moodle-dialogue-content .moodle-dialogue-ft{padding:0;margin:.7em 1em;font-size:12px;text-align:right;background-color:#FFF}.moodle-dialogue-confirm .confirmation-message{margin:.5em 1em}.moodle-dialogue-confirm .confirmation-dialogue input{min-width:80px}.moodle-dialogue-exception .moodle-exception-message{margin:1em}.moodle-dialogue-exception .moodle-exception-param{margin-bottom:.5em}.moodle-dialogue-exception .moodle-exception-param label{width:150px}.moodle-dialogue-exception .param-stacktrace label{display:block;padding:4px 1em;margin:0}.moodle-dialogue-exception .param-stacktrace pre{display:block;height:200px;overflow:auto}.moodle-dialogue-exception .param-stacktrace .stacktrace-file{display:inline-block;margin:4px 0}.moodle-dialogue-exception .param-stacktrace .stacktrace-line{display:inline-block;width:50px;margin:4px 1em}.moodle-dialogue-exception .param-stacktrace .stacktrace-call{padding-bottom:4px;padding-left:25px;margin-bottom:4px}.moodle-dialogue .moodle-dialogue-bd .content-lightbox{top:0;left:0;width:100%;height:100%;padding:10% 0;text-align:center;background-color:white;opacity:.75;filter:alpha(opacity=75)}.moodle-dialogue .tooltiptext{max-height:300px}.moodle-dialogue-base .moodle-dialogue.moodle-dialogue-tooltip{z-index:3001}#page-question-edit.dir-rtl a.container-close{right:auto;left:6px}.chooserdialoguebody,.choosertitle{display:none}.moodle-dialogue.chooserdialogue .moodle-dialogue-content .moodle-dialogue-ft{margin:0}.chooserdialogue .moodle-dialogue-wrap .moodle-dialogue-bd{padding:0;background:#f2f2f2;-webkit-border-bottom-right-radius:10px;border-bottom-right-radius:10px;-webkit-border-bottom-left-radius:10px;border-bottom-left-radius:10px;-moz-border-radius-bottomright:10px;-moz-border-radius-bottomleft:10px}.choosercontainer #chooseform .submitbuttons{margin:.7em 0;text-align:center}.choosercontainer #chooseform .submitbuttons input{min-width:100px;margin:0 .5em}.choosercontainer #chooseform .options{position:relative;border-bottom:1px solid #bbb}.jsenabled .choosercontainer #chooseform .alloptions{max-width:20.3em;overflow-x:hidden;overflow-y:auto;-webkit-box-shadow:inset 0 0 30px 0 #ccc;-moz-box-shadow:inset 0 0 30px 0 #ccc;box-shadow:inset 0 0 30px 0 #ccc}.dir-rtl.jsenabled .choosercontainer #chooseform .alloptions{max-width:18.3em}.choosercontainer #chooseform .moduletypetitle,.choosercontainer #chooseform .option,.choosercontainer #chooseform .nonoption{padding:0 1.6em 0 1.6em;margin-bottom:0}.choosercontainer #chooseform .moduletypetitle{padding-top:1.2em;padding-bottom:.4em;text-transform:uppercase}.choosercontainer #chooseform .option .typename,.choosercontainer #chooseform .option span.modicon img.icon,.choosercontainer #chooseform .nonoption .typename,.choosercontainer #chooseform .nonoption span.modicon img.icon{padding:0 0 0 .5em}.dir-rtl .choosercontainer #chooseform .option .typename,.dir-rtl .choosercontainer #chooseform .option span.modicon img.icon,.dir-rtl .choosercontainer #chooseform .nonoption .typename,.dir-rtl .choosercontainer #chooseform .nonoption span.modicon img.icon{padding:0 .5em 0 0}.choosercontainer #chooseform .option span.modicon img.icon,.choosercontainer #chooseform .nonoption span.modicon img.icon{width:24px;height:24px}.choosercontainer #chooseform .option input[type=radio],.choosercontainer #chooseform .option span.typename,.choosercontainer #chooseform .option span.modicon{vertical-align:middle}.choosercontainer #chooseform .option label{display:block;padding:.3em 0 .1em 0;border-bottom:1px solid #fff}.choosercontainer #chooseform .nonoption{padding-top:.3em;padding-bottom:.1em;padding-left:2.7em}.dir-rtl .choosercontainer #chooseform .nonoption{padding-right:2.7em;padding-left:0}.choosercontainer #chooseform .subtype{padding:0 1.6em 0 3.2em;margin-bottom:0}.dir-rtl .choosercontainer #chooseform .subtype{padding:0 3.2em 0 1.6em}.choosercontainer #chooseform .subtype .typename{margin:0 0 0 .2em}.dir-rtl .choosercontainer #chooseform .subtype .typename{margin:0 .2em 0 0}.jsenabled .choosercontainer #chooseform .instruction,.jsenabled .choosercontainer #chooseform .typesummary{position:absolute;top:0;right:0;bottom:0;left:20.3em;display:none;padding:1.6em;margin:0;overflow-x:hidden;overflow-y:auto;line-height:2em;background-color:#fff}.dir-rtl.jsenabled .choosercontainer #chooseform .instruction,.dir-rtl.jsenabled .choosercontainer #chooseform .typesummary{right:18.5em;left:0;border-right:1px solid grey}.jsenabled .choosercontainer #chooseform .instruction,.choosercontainer #chooseform .selected .typesummary{display:block}.choosercontainer #chooseform .selected{background-color:#fff;-webkit-box-shadow:0 0 10px 0 #ccc;-moz-box-shadow:0 0 10px 0 #ccc;box-shadow:0 0 10px 0 #ccc}.section-modchooser-link img.smallicon{padding:3px}.formlistingradio{padding-right:10px;padding-bottom:25px}.formlistinginputradio{float:left}.formlistingmain{min-height:225px}.formlisting{position:relative;padding:1px 19px 14px;margin:15px 0;background-color:white;border:1px solid #DDD;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.formlistingmore{position:absolute;right:-1px;bottom:-1px;padding:3px 7px;font-size:12px;font-weight:bold;color:#9da0a4;cursor:pointer;background-color:whiteSmoke;border:1px solid #ddd;-webkit-border-radius:4px 0 4px 0;-moz-border-radius:4px 0 4px 0;border-radius:4px 0 4px 0}.formlistingall{padding:0;margin:15px 0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.formlistingrow{top:50%;left:50%;float:left;width:150px;min-height:34px;padding:6px;cursor:pointer;background-color:#f7f7f9;border-right:1px solid #e1e1e8;border-bottom:1px solid;border-left:1px solid #e1e1e8;border-color:#e1e1e8;-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px}body.jsenabled .formlistingradio{display:none}body.jsenabled .formlisting{display:block}table.collection{width:100%;margin-bottom:20px;border:1px solid #ddd;border-collapse:separate;*border-collapse:collapse;border-left:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}table.collection th,table.collection td{padding:8px;line-height:20px;text-align:left;vertical-align:top;border-top:1px solid #ddd}table.collection th{font-weight:bold}table.collection thead th{vertical-align:bottom}table.collection caption+thead tr:first-child th,table.collection caption+thead tr:first-child td,table.collection colgroup+thead tr:first-child th,table.collection colgroup+thead tr:first-child td,table.collection thead:first-child tr:first-child th,table.collection thead:first-child tr:first-child td{border-top:0}table.collection tbody+tbody{border-top:2px solid #ddd}table.collection .table{background-color:#fff}table.collection th,table.collection td{border-left:1px solid #ddd}table.collection caption+thead tr:first-child th,table.collection caption+tbody tr:first-child th,table.collection caption+tbody tr:first-child td,table.collection colgroup+thead tr:first-child th,table.collection colgroup+tbody tr:first-child th,table.collection colgroup+tbody tr:first-child td,table.collection thead:first-child tr:first-child th,table.collection tbody:first-child tr:first-child th,table.collection tbody:first-child tr:first-child td{border-top:0}table.collection thead:first-child tr:first-child>th:first-child,table.collection tbody:first-child tr:first-child>td:first-child,table.collection tbody:first-child tr:first-child>th:first-child{-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topleft:4px}table.collection thead:first-child tr:first-child>th:last-child,table.collection tbody:first-child tr:first-child>td:last-child,table.collection tbody:first-child tr:first-child>th:last-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-topright:4px}table.collection thead:last-child tr:last-child>th:first-child,table.collection tbody:last-child tr:last-child>td:first-child,table.collection tbody:last-child tr:last-child>th:first-child,table.collection tfoot:last-child tr:last-child>td:first-child,table.collection tfoot:last-child tr:last-child>th:first-child{-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px}table.collection thead:last-child tr:last-child>th:last-child,table.collection tbody:last-child tr:last-child>td:last-child,table.collection tbody:last-child tr:last-child>th:last-child,table.collection tfoot:last-child tr:last-child>td:last-child,table.collection tfoot:last-child tr:last-child>th:last-child{-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px}table.collection tfoot+tbody:last-child tr:last-child td:first-child{-webkit-border-bottom-left-radius:0;border-bottom-left-radius:0;-moz-border-radius-bottomleft:0}table.collection tfoot+tbody:last-child tr:last-child td:last-child{-webkit-border-bottom-right-radius:0;border-bottom-right-radius:0;-moz-border-radius-bottomright:0}table.collection caption+thead tr:first-child th:first-child,table.collection caption+tbody tr:first-child td:first-child,table.collection colgroup+thead tr:first-child th:first-child,table.collection colgroup+tbody tr:first-child td:first-child{-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topleft:4px}table.collection caption+thead tr:first-child th:last-child,table.collection caption+tbody tr:first-child td:last-child,table.collection colgroup+thead tr:first-child th:last-child,table.collection colgroup+tbody tr:first-child td:last-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-topright:4px}table.collection tbody>tr:nth-child(odd)>td,table.collection tbody>tr:nth-child(odd)>th{background-color:#f9f9f9}table.collection .name{text-align:left;vertical-align:middle}table.collection .awards{width:10%;text-align:center;vertical-align:middle}table.collection .criteria{width:40%;text-align:left;vertical-align:top}table.collection .badgeimage,table.collection .status{width:15%;text-align:center;vertical-align:middle}table.collection .description{width:25%;text-align:left}table.collection .actions{width:11em;text-align:center;vertical-align:middle}a.criteria-action{float:right;padding:0 3px}table.issuedbadgebox{width:750px;background-color:#fff}table.badgeissuedimage{width:150px;text-align:center}table.badgeissuedinfo{width:600px}table.badgeissuedinfo .bvalue{text-align:left;vertical-align:middle}table.badgeissuedinfo .bfield{width:125px;font-style:italic;text-align:left}ul.badges{margin:0;list-style:none}.badges li{position:relative;display:inline-block;width:150px;padding-bottom:2em;text-align:center;vertical-align:top}.badges li .badge-name{display:block;padding:5px}.badges li>img{position:absolute}.badges li .badge-image{top:0;left:10px;z-index:1;width:90px;height:90px}.badges li .badge-actions{position:relative}div.badge{position:relative;display:block}div.badge .expireimage{top:0;left:20px;width:100px;height:100px}.expireimage{position:absolute;top:0;left:30px;z-index:10;width:90px;height:90px;opacity:.85;filter:alpha(opacity=85)}.badge-profile{vertical-align:top}.connected{color:#468847}.notconnected{color:#b94a48}#page-badges-award .recipienttable tr td{vertical-align:top}#page-badges-award .recipienttable tr td.actions .actionbutton{width:100%;padding:.5em 0;margin:.3em 0}#page-badges-award .recipienttable tr td.existing,#page-badges-award .recipienttable tr td.potential{width:42%}.statustable{margin-bottom:0}.statusbox.active{background-color:#dff0d8}.statusbox.inactive{background-color:#fcf8e3}.activatebadge{margin:0;text-align:left;vertical-align:middle}.addcourse{float:right}.invisiblefieldset{display:inline;padding:0;margin:0;border-width:0}.breadcrumb-nav{float:left;margin-bottom:10px}.dir-rtl .breadcrumb-nav{float:right}.breadcrumb-button .singlebutton div{margin-right:0}.breadcrumb-nav .breadcrumb{margin:0}.moodle-actionmenu,.moodle-actionmenu>ul,.moodle-actionmenu>ul>li{display:inline-block}.moodle-actionmenu ul{padding:0;margin:0;list-style-type:none}.moodle-actionmenu .toggle-display,.moodle-actionmenu .menu-action-text{display:none}.jsenabled .block .editing_move{display:none}.jsenabled .moodle-actionmenu[data-enhance]{display:block}.jsenabled .moodle-actionmenu[data-enhance] .menu{display:none}.jsenabled .moodle-actionmenu[data-enhance] .toggle-display{display:inline;opacity:.5;filter:alpha(opacity=50)}.jsenabled .moodle-actionmenu[data-enhanced] .toggle-display{opacity:1;filter:alpha(opacity=100)}.jsenabled .moodle-actionmenu[data-enhanced] .menu-action-text{display:inline}.moodle-actionmenu[data-enhanced].show{position:relative}.moodle-actionmenu[data-enhanced].show .menu{position:absolute;z-index:1000;display:block;text-align:left;background-color:#fff;border:1px solid #ccc;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;-webkit-box-shadow:5px 5px 20px 0 #666;-moz-box-shadow:5px 5px 20px 0 #666;box-shadow:5px 5px 20px 0 #666}.moodle-actionmenu[data-enhanced].show .menu a{display:block;padding:2px 1em 2px .5em;color:#333}.moodle-actionmenu[data-enhanced].show .menu a:hover,.moodle-actionmenu[data-enhanced].show .menu a:focus{color:#fff;background-color:#08c}.moodle-actionmenu[data-enhanced].show .menu a:first-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-topleft:4px}.moodle-actionmenu[data-enhanced].show .menu a:last-child{-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomright:4px;-moz-border-radius-bottomleft:4px}.moodle-actionmenu[data-enhanced].show .menu a.hidden{display:none}.moodle-actionmenu[data-enhanced].show .menu img{vertical-align:middle}.moodle-actionmenu[data-enhanced].show .menu .iconsmall{margin-right:8px}.moodle-actionmenu[data-enhanced].show .menu>li{display:block}.moodle-actionmenu[data-enhanced].show .menu.align-tl-bl{top:100%;left:0;margin-top:4px}.moodle-actionmenu[data-enhanced].show .menu.align-tr-bl{top:100%;right:100%}.moodle-actionmenu[data-enhanced].show .menu.align-bl-bl{bottom:100%;left:0}.moodle-actionmenu[data-enhanced].show .menu.align-br-bl{right:100%;bottom:100%}.moodle-actionmenu[data-enhanced].show .menu.align-tl-br{top:100%;left:100%}.moodle-actionmenu[data-enhanced].show .menu.align-tr-br{top:100%;right:0;margin-top:4px}.moodle-actionmenu[data-enhanced].show .menu.align-bl-br{bottom:100%;left:100%}.moodle-actionmenu[data-enhanced].show .menu.align-br-br{right:0;bottom:100%}.moodle-actionmenu[data-enhanced].show .menu.align-tl-tl{top:0;left:0}.moodle-actionmenu[data-enhanced].show .menu.align-tr-tl{top:0;right:100%;margin-right:4px}.moodle-actionmenu[data-enhanced].show .menu.align-bl-tl{bottom:100%;left:0;margin-bottom:4px}.moodle-actionmenu[data-enhanced].show .menu.align-br-tl{right:100%;bottom:100%}.moodle-actionmenu[data-enhanced].show .menu.align-tl-tr{top:0;left:100%;margin-left:4px}.moodle-actionmenu[data-enhanced].show .menu.align-tr-tr{top:0;right:0}.moodle-actionmenu[data-enhanced].show .menu.align-bl-tr{bottom:100%;left:100%}.moodle-actionmenu[data-enhanced].show .menu.align-br-tr{right:0;bottom:100%;margin-bottom:4px}.action-menu-shown .moodle-actionmenu[data-enhanced] .toggle-display{background-color:#FFF}.block .moodle-actionmenu{text-align:right}.dir-rtl .moodle-actionmenu[data-enhanced].show .menu{right:auto;left:0;text-align:right}.dir-rtl .moodle-actionmenu[data-enhanced].show .menu .iconsmall{margin-right:0;margin-left:8px}.dir-rtl .moodle-actionmenu[data-enhanced].show .menu.align-tl-bl{right:0;left:auto}.dir-rtl .moodle-actionmenu[data-enhanced].show .menu.align-tr-bl{right:auto;left:100%}.dir-rtl .moodle-actionmenu[data-enhanced].show .menu.align-bl-bl{right:0;left:auto}.dir-rtl .moodle-actionmenu[data-enhanced].show .menu.align-br-bl{right:auto;left:100%}.dir-rtl .moodle-actionmenu[data-enhanced].show .menu.align-tl-br{right:100%;left:auto}.dir-rtl .moodle-actionmenu[data-enhanced].show .menu.align-tr-br{right:auto;left:0}.dir-rtl .moodle-actionmenu[data-enhanced].show .menu.align-bl-br{right:100%;left:auto}.dir-rtl .moodle-actionmenu[data-enhanced].show .menu.align-br-br{right:auto;left:0}.dir-rtl .moodle-actionmenu[data-enhanced].show .menu.align-tl-tl{right:0;left:auto}.dir-rtl .moodle-actionmenu[data-enhanced].show .menu.align-tr-tl{right:auto;left:100%}.dir-rtl .moodle-actionmenu[data-enhanced].show .menu.align-bl-tl{right:0;left:auto}.dir-rtl .moodle-actionmenu[data-enhanced].show .menu.align-br-tl{right:auto;left:100%}.dir-rtl .moodle-actionmenu[data-enhanced].show .menu.align-tl-tr{right:100%;left:auto}.dir-rtl .moodle-actionmenu[data-enhanced].show .menu.align-tr-tr{right:auto;left:0}.dir-rtl .moodle-actionmenu[data-enhanced].show .menu.align-bl-tr{right:100%;left:auto}.dir-rtl .moodle-actionmenu[data-enhanced].show .menu.align-br-tr{right:auto;left:0}.dir-rtl .block .moodle-actionmenu{text-align:right}.formtable tbody th{font-weight:normal;text-align:right}.path-admin #assignrole{width:60%;margin-right:auto;margin-left:auto}.path-admin .admintable .leftalign{text-align:left}.environmenttable p.warn{color:#c09853;background-color:#fcf8e3}.environmenttable .error,.environmenttable span.warn,.environmenttable .ok{display:inline-block;padding:2px 4px;font-size:11.844px;font-weight:bold;line-height:14px;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);white-space:nowrap;vertical-align:baseline;background-color:#999;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.environmenttable .error:empty,.environmenttable span.warn:empty,.environmenttable .ok:empty{display:none}.environmenttable .error-important,.environmenttable span.warn-important,.environmenttable .ok-important{background-color:#b94a48}.environmenttable .error-important[href],.environmenttable span.warn-important[href],.environmenttable .ok-important[href]{background-color:#953b39}.environmenttable .error-warning,.environmenttable span.warn-warning,.environmenttable .ok-warning{background-color:#f89406}.environmenttable .error-warning[href],.environmenttable span.warn-warning[href],.environmenttable .ok-warning[href]{background-color:#c67605}.environmenttable .error-success,.environmenttable span.warn-success,.environmenttable .ok-success{background-color:#468847}.environmenttable .error-success[href],.environmenttable span.warn-success[href],.environmenttable .ok-success[href]{background-color:#356635}.environmenttable .error-info,.environmenttable span.warn-info,.environmenttable .ok-info{background-color:#3a87ad}.environmenttable .error-info[href],.environmenttable span.warn-info[href],.environmenttable .ok-info[href]{background-color:#2d6987}.environmenttable .error-inverse,.environmenttable span.warn-inverse,.environmenttable .ok-inverse{background-color:#333}.environmenttable .error-inverse[href],.environmenttable span.warn-inverse[href],.environmenttable .ok-inverse[href]{background-color:#1a1a1a}.environmenttable .error{background-color:#b94a48}.environmenttable span.warn{background-color:#f89406}.environmenttable .ok{background-color:#468847}.path-admin .admintable.environmenttable .name,.path-admin .admintable.environmenttable .info,.path-admin #assignrole .admintable .role,.path-admin #assignrole .admintable .userrole,.path-admin #assignrole .admintable .roleholder{white-space:nowrap}.path-admin .incompatibleblockstable td.c0{font-weight:bold}#page-admin-course-category .addcategory{padding:10px}#page-admin-course-index .editcourse{margin:20px auto}#page-admin-course-index .editcourse th,#page-admin-course-index .editcourse td{padding-right:10px;padding-left:10px}.timewarninghidden{display:none}.statusok,.statuswarning,.statusserious,.statuscritical{display:inline-block;padding:2px 4px;font-size:11.844px;font-weight:bold;line-height:14px;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);white-space:nowrap;vertical-align:baseline;background-color:#999;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.statusok:empty,.statuswarning:empty,.statusserious:empty,.statuscritical:empty{display:none}.statusok-important,.statuswarning-important,.statusserious-important,.statuscritical-important{background-color:#b94a48}.statusok-important[href],.statuswarning-important[href],.statusserious-important[href],.statuscritical-important[href]{background-color:#953b39}.statusok-warning,.statuswarning-warning,.statusserious-warning,.statuscritical-warning{background-color:#f89406}.statusok-warning[href],.statuswarning-warning[href],.statusserious-warning[href],.statuscritical-warning[href]{background-color:#c67605}.statusok-success,.statuswarning-success,.statusserious-success,.statuscritical-success{background-color:#468847}.statusok-success[href],.statuswarning-success[href],.statusserious-success[href],.statuscritical-success[href]{background-color:#356635}.statusok-info,.statuswarning-info,.statusserious-info,.statuscritical-info{background-color:#3a87ad}.statusok-info[href],.statuswarning-info[href],.statusserious-info[href],.statuscritical-info[href]{background-color:#2d6987}.statusok-inverse,.statuswarning-inverse,.statusserious-inverse,.statuscritical-inverse{background-color:#333}.statusok-inverse[href],.statuswarning-inverse[href],.statusserious-inverse[href],.statuscritical-inverse[href]{background-color:#1a1a1a}.statusok{background-color:#468847}.statuswarning{background-color:#c09853}.statusserious{background-color:#f89406}.statuscritical{background-color:#b94a48}#page-admin-report-capability-index #capabilitysearch{width:30em}#page-admin-report-backups-index .backup-error,#page-admin-report-backups-index .backup-unfinished{color:#b94a48}#page-admin-report-backups-index .backup-skipped,#page-admin-report-backups-index .backup-ok{color:#468847}#page-admin-report-backups-index .backup-warning{color:#c09853}#page-admin-qtypes .disabled,#page-admin-qbehaviours .disabled{color:#999}#page-admin-qtypes #qtypes div,#page-admin-qtypes #qtypes form,#page-admin-qbehaviours #qbehaviours div,#page-admin-qbehaviours #qbehaviours form{display:inline}#page-admin-qtypes #qtypes img.spacer,#page-admin-qbehaviours #qbehaviours img.spacer{width:16px}img.iconsmall{padding:.3em;margin:0}#page-admin-qbehaviours .cell.c3,#page-admin-qtypes .cell.c3{font-size:10.5px}#page-admin-lang .generalbox,#page-admin-course-index .singlebutton,#page-admin-course-index .addcategory,#page-course-index .buttons,#page-course-index-category .buttons,#page-admin-course-category .addcategory,#page-admin-stickyblocks .generalbox,#page-admin-maintenance .buttons,#page-admin-course-index .buttons,#page-admin-course-category .buttons,#page-admin-index .copyright,#page-admin-index .copyrightnotice,#page-admin-index .adminerror,#page-admin-index .availableupdatesinfo,#page-admin-index .adminerror .singlebutton,#page-admin-index .adminwarning .singlebutton,#page-admin-index #layout-table .singlebutton{margin-bottom:1em;text-align:center}.path-admin-roles .capabilitysearchui{margin-right:auto;margin-left:auto;text-align:left}#page-admin-roles-define .topfields{margin:1em 0 2em}#page-admin-roles-define .capdefault{background-color:#eee;border:1px solid #cecece}#page-filter-manage .backlink,.path-admin-roles .backlink{margin-top:1em}#page-admin-roles-explain #chooseuser h3,#page-admin-roles-usersroles .contextname{margin-top:0}#page-admin-roles-explain #chooseusersubmit{margin-top:0;text-align:center}#page-admin-roles-usersroles p{margin:0}#page-admin-roles-override .cell.c1,#page-admin-roles-assign .cell.c3,#page-admin-roles-assign .cell.c1{padding-top:.75em}#page-admin-roles-override .overridenotice,#page-admin-roles-define .definenotice{margin:1em 10% 2em 10%;text-align:left}#notice{width:60%;min-width:220px;margin:auto}#page-admin-index .releasenoteslink,#page-admin-index .adminwarning,#page-admin-index .maturitywarning,#page-admin-index .maturityinfo{width:60%;min-width:220px;padding:8px 35px 8px 14px;margin:auto;margin-bottom:20px;color:#c09853;text-shadow:0 1px 0 rgba(255,255,255,0.5);background-color:#fcf8e3;border:1px solid #fbeed5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}#page-admin-index .maturitywarning,#page-admin-index .adminwarning.maturityinfo.maturity50{color:#b94a48;background-color:#f2dede;border-color:#eed3d7}#page-admin-index .adminwarning.availableupdatesinfo,#page-admin-index .releasenoteslink{color:#3a87ad;background-color:#d9edf7;border-color:#bce8f1}#page-admin-index .adminwarning.availableupdatesinfo .moodleupdateinfo span{display:block}#page-admin-index .updateplugin div,#page-admin-plugins .updateplugin div{margin-bottom:.5em}#page-admin-index .updateplugin .updatepluginconfirmexternal,#page-admin-plugins .updateplugin .updatepluginconfirmexternal{padding:1em;background-color:#f2dede;border:1px solid #eed3d7}#page-admin-user-user_bulk #users .fgroup{white-space:nowrap}#page-admin-report-stats-index .graph{margin-bottom:1em;text-align:center}#page-admin-report-courseoverview-index .graph{margin-bottom:1em;text-align:center}#page-admin-lang .translator{border-style:solid;border-width:1px}.path-admin .roleassigntable{width:100%}.path-admin .roleassigntable td{padding:.2em .3em;vertical-align:top}.path-admin .roleassigntable p{margin:.2em 0;text-align:left}.path-admin .roleassigntable #existingcell,.path-admin .roleassigntable #potentialcell{width:42%}.path-admin .roleassigntable #existingcell p>label:first-child,.path-admin .roleassigntable #potentialcell p>label:first-child{font-weight:bold}.path-admin .roleassigntable #buttonscell{width:16%}.path-admin .roleassigntable #buttonscell #assignoptions{font-size:10.5px}.path-admin .roleassigntable #removeselect_wrapper,.path-admin .roleassigntable #addselect_wrapper{width:100%}.path-admin table.rolecap tr.rolecap th{font-weight:normal;text-align:left}.path-admin.dir-rtl table.rolecap tr.rolecap th{text-align:right}.path-admin .rolecap .hiddenrow{display:none}.path-admin #defineroletable .rolecap .inherit,.path-admin #defineroletable .rolecap .allow,.path-admin #defineroletable .rolecap .prevent,.path-admin #defineroletable .rolecap .prohibit{min-width:3.5em;padding:0;text-align:center}.path-admin .rolecap .cap-name,.path-admin .rolecap .note{display:block;font-size:10.5px;font-weight:normal;white-space:nowrap}.path-admin .rolecap label{display:block;padding:.5em;margin:0;text-align:center}.plugincheckwrapper{width:100%}.environmentbox{margin-top:1em}#mnetconfig table{margin-right:auto;margin-left:auto}.environmenttable .cell{padding:.15em .5em}.environmenttable img.iconhelp{padding-right:.3em}.dir-rtl .environmenttable img.iconhelp{padding-right:0;padding-left:.3em}#trustedhosts .generaltable{width:500px;margin-right:auto;margin-left:auto}#trustedhosts .standard{width:auto}#adminsettings legend{display:none}#adminsettings fieldset.error{margin:.2em 0 .5em 0}#adminsettings fieldset.error legend{display:block}.dir-rtl #admin-spelllanguagelist textarea,#page-admin-setting-editorsettingstinymce.dir-rtl .form-textarea textarea{text-align:left;direction:ltr}.adminsettingsflags{float:right}.dir-rtl .adminsettingsflags{float:left}.adminsettingsflags label{margin-right:7px}.dir-rtl .adminsettingsflags label{margin-left:7px}.form-description{clear:right}.dir-rtl .form-description{clear:left}.form-item .form-setting .form-htmlarea{display:inline;width:640px}.form-item .form-setting .form-htmlarea .htmlarea{display:block;width:640px}.form-item .form-setting .form-multicheckbox ul{padding:0;margin:7px 0 0 0;list-style:none}.form-item .form-setting .defaultsnext{display:inline;margin-right:.5em}.dir-rtl .form-item .form-setting .defaultsnext{margin-right:0;margin-left:.5em}.form-item .form-setting .locked-checkbox{display:inline;margin-right:.2em;margin-left:.5em}.dir-rtl .form-item .form-setting .locked-checkbox{display:inline;margin-right:.5em;margin-left:.2em}.form-item .form-setting .form-password .unmask,.form-item .form-setting .form-defaultinfo{display:inline-block}.form-item .pathok,.form-item .patherror{margin-left:.5em}#admin-devicedetectregex table{border:0}#admin-emoticons td input{width:8em}#admin-emoticons td.c0 input{width:4em}#adminthemeselector .selectedtheme td.c0{border:1px solid;border-right-width:0}#adminthemeselector .selectedtheme td.c1{border:1px solid;border-left-width:0}.admin_colourpicker,.admin_colourpicker_preview{display:none}.jsenabled .admin_colourpicker_preview{display:inline}.jsenabled .admin_colourpicker{display:block;width:410px;height:102px;margin-bottom:10px}.admin_colourpicker .loadingicon{margin-left:auto;vertical-align:middle}.admin_colourpicker .colourdialogue{float:left;border:1px solid #000}.admin_colourpicker .previewcolour{margin-left:301px;border:1px solid #000}.admin_colourpicker .currentcolour{margin-left:301px;border:1px solid #000;border-top-width:0}.dir-rtl .form-item .form-setting,.dir-rtl .form-item .form-label,.dir-rtl .form-item .form-description,.dir-rtl.path-admin .roleassigntable p{text-align:right}#page-admin-index #notice .checkforupdates{text-align:center}#plugins-check-info{margin:1em;text-align:center}#plugins-check .displayname .pluginicon{width:16px}#plugins-check .status-new .status{background-color:#dff0d8}#page-admin-index .adminwarning.availableupdatesinfo .moodleupdateinfo.maturity200 .info.release,#plugins-check .status-upgrade .status,#plugins-check .status-delete .status{background-color:#d9edf7}#plugins-control-panel .extension .source,#page-admin-index .adminwarning.availableupdatesinfo .moodleupdateinfo.maturity100 .info.release,#page-admin-index .adminwarning.availableupdatesinfo .moodleupdateinfo.maturity150 .info.release,.pluginupdateinfo.maturity100,.pluginupdateinfo.maturity150,#plugins-check .extension .source{background-color:#fcf8e3}#page-admin-index .adminwarning.availableupdatesinfo .moodleupdateinfo.maturity50 .info.release,.pluginupdateinfo.maturity50,#plugins-check .requires-failed,#plugins-check .missingfromdisk .displayname,#plugins-check .status-missing .status,#plugins-check .status-downgrade .status{background-color:#f2dede}#plugins-control-panel .statusmsg{padding:3px;background-color:#eee;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}#plugins-control-panel .status-missing .pluginname{background-color:#f2dede}#plugins-control-panel .status-missing .statusmsg{color:#b94a48}#plugins-control-panel .status-new .pluginname{background-color:#dff0d8}#plugins-control-panel .status-new .statusmsg{color:#468847}#plugins-control-panel .disabled .availability{background-color:#eee}#plugins-check .standard .source,#plugins-check .status-nodb .status,#plugins-check .status-uptodate .status,#plugins-check .requires-ok{color:#999}#plugins-check .requires ul{margin:0;font-size:10.5px}#plugins-check .status .pluginupdateinfo{padding:5px 10px;margin:10px;background-color:#d9edf7;-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px}#plugins-check .status .pluginupdateinfo span,#plugins-check .status .pluginupdateinfo a{padding-right:1em}#page-admin-index .upgradepluginsinfo{text-align:center}#page-admin-plugins .checkforupdates{margin:0 auto 1em;text-align:center}#plugins-control-panel .requiredby,#plugins-control-panel .pluginname .componentname{font-size:11.9px;color:#999}#plugins-control-panel .pluginname .componentname{margin-left:22px}#plugins-overview-filter .filter-item,#plugins-overview-panel .info{padding:0 10px}#page-admin-index .adminwarning.availableupdatesinfo .moodleupdateinfo .separator,#plugins-check .status .pluginupdateinfo .separator,#page-admin-plugins .separator{border-left:1px dotted #999}#plugins-control-panel .msg td{text-align:center}#plugins-overview-filter,#plugins-overview-panel{margin:1em auto;text-align:center}#plugins-overview-panel .info.updatable{margin-left:10px;font-weight:bold;background-color:#d9edf7;-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px}#plugins-overview-filter .filter-item.active{font-weight:bold}#plugins-control-panel .displayname img.icon{padding-top:0;padding-bottom:0}#plugins-control-panel .uninstall a{color:#b94a48}#plugins-control-panel .notes .pluginupdateinfo{padding:5px 10px;margin:10px;background-color:#d9edf7;-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px}#plugins-control-panel .notes .pluginupdateinfo span,#plugins-control-panel .notes .pluginupdateinfo a{padding-right:1em}.dir-rtl #plugins-check .pluginupdateinfo{text-align:center;direction:ltr}.dir-rtl #plugins-check .rootdir,.dir-rtl #plugins-check .requires-ok{text-align:left;direction:ltr}#page-admin-mnet-peers .box.deletedhosts{margin-bottom:1em;font-size:11.9px}#page-admin-mnet-peers .mform .certdetails{background-color:white}#page-admin-mnet-peers .mform .deletedhostinfo{padding:4px;margin-bottom:5px;background-color:#f2dede;border:2px solid #eed3d7}#core-cache-plugin-summaries table,#core-cache-store-summaries table{width:100%}#core-cache-lock-summary table,#core-cache-definition-summaries table,#core-cache-mode-mappings table{margin:0 auto}#core-cache-store-summaries .default-store td{font-style:italic;color:#333}#core-cache-rescan-definitions,#core-cache-mode-mappings .edit-link,#core-cache-lock-summary .new-instance{margin-top:.5em;text-align:center}.tinymcesubplugins img.icon{padding-top:0;padding-bottom:0}#page-admin-roles-assign div.box.generalbox{padding:8px 35px 8px 14px;margin-bottom:20px;color:#c09853;color:#b94a48;text-shadow:0 1px 0 rgba(255,255,255,0.5);background-color:#fcf8e3;background-color:#f2dede;border:1px solid #fbeed5;border-color:#eed3d7;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.calendartable{width:100%}.calendartable th,.calendartable td{width:14%;text-align:center;vertical-align:top;border:0}.calendar_event_course{background-color:#ffd3bd}.calendar_event_global{background-color:#d6f8cd}.calendar_event_group{background-color:#fee7ae}.calendar_event_user{background-color:#dce7ec}.path-calendar .calendar-controls .previous,.path-calendar .calendar-controls .next,.path-calendar .calendar-controls .current{display:block;float:left;width:12%}.path-calendar .calendar-controls .previous{text-align:left}.path-calendar .calendar-controls .current{width:76%;text-align:center}.path-calendar .calendar-controls .next{text-align:right}.path-calendar .maincalendar{padding:0;vertical-align:top}.path-calendar .maincalendar .bottom{padding:5px 0 0 0;text-align:center}.path-calendar .maincalendar .heightcontainer{position:relative;height:100%}.path-calendar .maincalendar .calendarmonth{width:98%;margin:10px auto}.path-calendar .maincalendar .calendarmonth ul{margin:0}.path-calendar .maincalendar .calendarmonth ul li{margin-top:4px;list-style-type:none}.path-calendar .maincalendar .calendarmonth td{height:5em}.path-calendar .maincalendar .calendar-controls .previous,.path-calendar .maincalendar .calendar-controls .next{width:30%}.path-calendar .maincalendar .calendar-controls .current{width:39.95%}.path-calendar .maincalendar .controls{width:98%;margin:10px auto}.path-calendar .maincalendar .eventlist .event{width:100%;margin-bottom:10px;border-collapse:separate;border-spacing:0;border-style:solid;border-width:1px}.path-calendar .maincalendar .eventlist .event .topic .name{float:left}.dir-rtl.path-calendar .maincalendar .eventlist .event .topic .name,.path-calendar .maincalendar .eventlist .event .topic .date{float:right}.dir-rtl.path-calendar .maincalendar .eventlist .event .topic .date{float:left}.path-calendar .maincalendar .eventlist .event .subscription,.path-calendar .maincalendar .eventlist .event .course{float:left;clear:left}.dir-rtl.path-calendar .maincalendar .eventlist .event .subscription,.dir-rtl.path-calendar .maincalendar .eventlist .event .course{float:right;clear:right}.path-calendar .maincalendar .eventlist .event .side{width:32px}.path-calendar .maincalendar .eventlist .event .commands a{margin:0 3px}.path-calendar .maincalendar .header{overflow:hidden}.path-calendar .maincalendar .header .buttons{float:right}.dir-rtl.path-calendar .maincalendar .header .buttons{float:left}.path-calendar .filters table{width:100%;border-collapse:separate;border-spacing:2px}#page-calendar-export .indent{padding-left:20px}.path-calendar .cal_courses_flt label{margin-right:.45em}.dir-rtl.path-calendar .cal_courses_flt label{margin-right:0;margin-left:.45em}.block .minicalendar th,.block .minicalendar td{padding:2px;font-size:.8em}.block .minicalendar{max-width:280px;margin-right:auto;margin-left:auto}.block .minicalendar td.weekend{color:#A00}.block .calendar-controls .previous{display:block;float:left;width:12%;text-align:left}.block .calendar-controls .current{display:block;float:left;width:76%;text-align:center}.block .calendar-controls .next{display:block;float:left;width:12%;text-align:right}.block .calendar_filters ul{margin:0;list-style:none}.block .calendar_filters li{margin-bottom:.2em}.block .calendar_filters li span img{padding:0 .2em}.block .calendar_filters .eventname{padding-left:.2em}.dir-rtl .block .calendar_filters .eventname{padding-right:.2em;padding-left:0}.block .content h3.eventskey{margin-top:.5em}@media(min-width:768px){#page-calender-view .container fluid{min-width:1024px}}.section_add_menus{text-align:right}.dir-rtl .section_add_menus{text-align:left}.section_add_menus .horizontal div,.section_add_menus .horizontal form{display:inline}.section_add_menus optgroup{font-style:italic;font-weight:normal}.section_add_menus .urlselect{margin-left:.4em}.dir-rtl .section_add_menus .urlselect{margin-right:.4em;margin-left:0}.section_add_menus .urlselect select{margin-left:.2em}.dir-rtl .section_add_menus .urlselect select{margin-right:.2em;margin-left:0}.section_add_menus .urlselect img.iconhelp{padding:0;margin:0;vertical-align:text-bottom}.site-topic ul.section,.course-content ul.section{margin:1em}.section .activity img.activityicon{margin-right:6px}.dir-rtl .section .activity img.activityicon{margin-right:0;margin-left:6px}.section .activity .activityinstance,.section .activity .activityinstance div{display:inline-block}.editing .section .activity .activityinstance{min-width:40%}.section .activity .activityinstance>a{display:block}.editing_show+.editing_assign,.editing_hide+.editing_assign{margin-left:20px}.section .activity .commands{display:inline;white-space:nowrap}.section .activity.modtype_label .commands{padding-left:22px;margin-left:40%}.section .activity.modtype_label.label{font-weight:normal}.section li.activity{padding:.2em;clear:both}.section .activity .activityinstance .groupinglabel{padding-left:30px}.dir-rtl .section .activity .activityinstance .groupinglabel{padding-right:30px}.section .activity .availabilityinfo,.section .activity .contentafterlink{margin-top:.5em;margin-left:30px}.dir-rtl .section .activity .availabilityinfo,.dir-rtl .section .activity .contentafterlink{margin-right:30px;margin-left:0}.section .activity .contentafterlink p{margin:.5em 0}.editing .section .activity:hover,.editing .section .activity.action-menu-shown{background-color:#eee}.course-content .current{background-color:#d9edf7}.course-content .section-summary{margin-top:5px;list-style:none;border:1px solid #DDD}.course-content .section-summary .section-title{margin:2px 5px 10px 5px}.course-content .section-summary .summarytext{margin:2px 5px 2px 5px}.course-content .section-summary .section-summary-activities .activity-count{display:inline-block;margin:3px;font-size:11.9px;color:#999;white-space:nowrap}.course-content .section-summary .summary{margin-top:5px}.course-content .single-section{margin-top:1em}.course-content .single-section .section-navigation{display:block;padding:.5em;margin-bottom:-0.5em}.course-content .single-section .section-navigation .title{clear:both;font-size:108%;font-weight:bold}.course-content .single-section .section-navigation .mdl-left{float:left;margin-right:1em;font-weight:normal}.dir-rtl .course-content .single-section .section-navigation .mdl-left{float:right}.course-content .single-section .section-navigation .mdl-left .larrow{margin-right:.1em}.course-content .single-section .section-navigation .mdl-right{float:right;margin-left:1em;font-weight:normal}.dir-rtl .course-content .single-section .section-navigation .mdl-right{float:left}.course-content .single-section .section-navigation .mdl-right .rarrow{margin-left:.1em}.course-content .single-section .section-navigation .mdl-bottom{margin-top:0}.course-content ul li.section.main{margin-top:0;border-bottom:2px solid #eee}.course-content ul li.section.hidden{opacity:.5}.course-content ul.topics li.section .content,.course-content ul.weeks li.section .content{padding:0;margin-right:20px;margin-left:20px}.course-content{margin-top:0}.course-content ul.topics li.section{padding-bottom:20px}.course-content ul.topics li.section .summary{margin-left:25px}.path-course-view .completionprogress{margin-left:25px}.path-course-view .completionprogress{position:relative;z-index:1000;display:block;float:right;height:20px}#page-site-index .subscribelink{text-align:right}#page-site-index .headingblock{margin-bottom:9px}.path-course-view a.reduce-sections{padding-left:.2em}.path-course-view .headingblock{margin-bottom:9px}.path-course-view .subscribelink{text-align:right}.path-course-view .unread{margin-left:30px}.dir-rtl.path-course-view .unread{margin-right:30px}.path-course-view .block.drag .header{cursor:move}.path-course-view .completionprogress{text-align:right}.dir-rtl.path-course-view .completionprogress{text-align:left}.path-course-view .single-section .completionprogress{margin-right:5px}.path-course-view .section .summary{line-height:normal}.path-site li.activity>div,.path-course-view li.activity>div{position:relative}.path-course-view li.activity span.autocompletion,.path-course-view li.activity form.togglecompletion{float:right}.path-course-view li.activity form.togglecompletion .ajaxworking{width:16px;height:16px;background:url([[pix:i/ajaxloader]]) no-repeat}.dir-rtl.path-course-view li.activity form.togglecompletion,.dir-rtl.path-course-view li.activity span.autocompletion{float:left}.dir-rtl.path-course-view .completionprogress{float:none}.dir-rtl.path-course-view li.activity form.togglecompletion .ajaxworking{right:-22px}li.section.hidden span.commands a.editing_hide,li.section.hidden span.commands a.editing_show{cursor:default}ul.weeks h3.sectionname{white-space:nowrap}.editing ul.weeks h3.sectionname{white-space:normal}.section img.movetarget{width:80px;height:16px}input.titleeditor{width:330px;vertical-align:text-bottom}span.editinstructions{position:absolute;top:0;left:0;z-index:9999;padding:.1em .4em;margin-top:-22px;margin-left:30px;font-size:11.9px;line-height:16px;color:#3a87ad;text-decoration:none;background-color:#d9edf7;border:1px solid #bce8f1;-webkit-box-shadow:2px 2px 5px 1px #ccc;-moz-box-shadow:2px 2px 5px 1px #ccc;box-shadow:2px 2px 5px 1px #ccc}.dir-rtl span.editinstructions{right:32px;left:auto}#dndupload-status{position:absolute;z-index:9999;z-index:0;width:40%;padding:6px;margin:0 30%;color:#3a87ad;text-align:center;background:#d9edf7;border:1px solid #bce8f1;-webkit-border-bottom-right-radius:8px;border-bottom-right-radius:8px;-webkit-border-bottom-left-radius:8px;border-bottom-left-radius:8px;-moz-border-radius-bottomright:8px;-moz-border-radius-bottomleft:8px;-webkit-box-shadow:2px 2px 5px 1px #ccc;-moz-box-shadow:2px 2px 5px 1px #ccc;box-shadow:2px 2px 5px 1px #ccc}.dndupload-preview{padding:.3em;margin-top:.2em;color:#909090;list-style:none;border:1px dashed #909090}.dndupload-preview img.icon{padding:0;vertical-align:text-bottom}.dndupload-progress-outer{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f7f7f7;background-image:-moz-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f5f5f5),to(#f9f9f9));background-image:-webkit-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:-o-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:linear-gradient(to bottom,#f5f5f5,#f9f9f9);background-repeat:repeat-x;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5',endColorstr='#fff9f9f9',GradientType=0);-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1)}.dndupload-progress-inner{float:left;width:0;height:100%;font-size:12px;color:#fff;text-align:center;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#0e90d2;background-image:-moz-linear-gradient(top,#149bdf,#0480be);background-image:-webkit-gradient(linear,0 0,0 100%,from(#149bdf),to(#0480be));background-image:-webkit-linear-gradient(top,#149bdf,#0480be);background-image:-o-linear-gradient(top,#149bdf,#0480be);background-image:linear-gradient(to bottom,#149bdf,#0480be);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf',endColorstr='#ff0480be',GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-moz-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-transition:width .6s ease;-moz-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.dndupload-hidden{display:none}#page-course-pending .singlebutton,#page-course-index .singlebutton,#page-course-index-category .singlebutton,#page-course-editsection .singlebutton{text-align:center}#page-admin-course-manage #movecourses td img{margin:0 .22em;vertical-align:text-bottom}#page-admin-course-manage #movecourses td img.icon{padding:0}#coursesearch{margin-top:1em;text-align:center}#page-course-pending .pendingcourserequests{margin-bottom:1em}#page-course-pending .pendingcourserequests .singlebutton{display:inline}#page-course-pending .pendingcourserequests .cell{padding:0 5px}#page-course-pending .pendingcourserequests .cell.c6{white-space:nowrap}.coursebox{padding:5px;margin-bottom:15px;border:1px dotted #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.coursebox>.info>.name a{display:block;padding-left:21px;background-image:url([[pix:moodle|i/course]]);background-position:center left;background-repeat:no-repeat}.coursebox.remotehost>.info>.name a{background-image:url([[pix:moodle|i/mnethost]])}.coursebox>.info>.name,.coursebox .content .teachers,.coursebox .content .courseimage,.coursebox .content .coursefile{float:left;width:40%;clear:left}.coursebox>.info>h3.name{margin:5px}.coursebox>.info>.name{padding:0;margin:5px}.coursebox .content .teachers li{padding:0;margin:0;list-style-type:none}.coursebox .enrolmenticons{float:right;padding:3px 0}.coursebox .moreinfo{float:right;padding:3px 0}.coursebox .enrolmenticons img,.coursebox .moreinfo img{margin:0 .2em}.coursebox .content{clear:both}.coursebox .content .summary,.coursebox .content .coursecat{float:right;width:55%}.coursebox .content .coursecat{clear:right;text-align:right}.coursebox.remotecoursebox .remotecourseinfo{float:left;width:40%}.coursebox .content .courseimage img{max-width:100px;max-height:100px}.coursebox .content .coursecat,.coursebox .content .summary,.coursebox .content .courseimage,.coursebox .content .coursefile,.coursebox .content .teachers,.coursebox.remotecoursebox .remotecourseinfo{padding:0;margin:3px 5px}.dir-rtl .coursebox>.info>.name a{padding-right:21px;padding-left:0;background-position:center right}.dir-rtl .coursebox>.info>.name,.dir-rtl .coursebox .teachers,.dir-rtl .coursebox .content .courseimage,.dir-rtl .coursebox .content .coursefile{float:right;clear:right}.dir-rtl .coursebox .enrolmenticons,.dir-rtl .coursebox .moreinfo{float:left}.dir-rtl .coursebox .summary,.dir-rtl .coursebox .coursecat{float:left}.dir-rtl .coursebox .coursecat{clear:left;text-align:left}.coursebox.collapsed{margin-bottom:0}.coursebox.collapsed>.content{display:none}.courses .coursebox.collapsed{padding:3px 0;border:1px solid #eee}.courses .coursebox.even{background-color:#f6f6f6}.courses .coursebox:hover,.course_category_tree .courses>.paging.paging-morelink:hover{background-color:#eee}.course_category_tree .category .numberofcourse{font-size:11.9px}.course_category_tree .controls{visibility:hidden}.course_category_tree .controls div{display:inline;cursor:pointer}.jsenabled .course_category_tree .controls{visibility:visible}.course_category_tree .controls{float:right;margin-bottom:5px;text-align:right}.course_category_tree .controls div{padding-right:2em;font-size:75%}.course_category_tree .category>.info .name{padding:2px 18px;margin:3px;background-image:url([[pix:moodle|t/collapsed_empty]]);background-position:center left;background-repeat:no-repeat}.dir-rtl .course_category_tree .category>.info .name{background-image:url([[pix:moodle|t/collapsed_empty_rtl]]);background-position:center right}.course_category_tree .category.with_children>.info .name{background-image:url([[pix:moodle|t/expanded]])}.course_category_tree .category.with_children.collapsed>.info .name{background-image:url([[pix:moodle|t/collapsed]])}.dir-rtl .course_category_tree .category.with_children.collapsed>.info .name{background-image:url([[pix:moodle|t/collapsed_rtl]])}.course_category_tree .category.collapsed>.content{display:none}.course_category_tree .category>.info{min-height:20px;min-height:0;padding:19px;padding:0;margin:3px 0;margin-bottom:20px;margin-bottom:3px;background-color:#f5f5f5;border:1px solid #e3e3e3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.course_category_tree .category>.info blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}.course_category_tree.frontpage-category-names .category>.info{margin:0;background:0;border:0}.course_category_tree .category>.content{padding-left:16px}.dir-rtl .course_category_tree .category>.content{padding-right:16px;padding-left:0}.course_category_tree .subcategories>.paging,.courses>.paging{padding:5px;margin:0;text-align:center}.courses>.paging.paging-morelink,.course_category_tree .subcategories>.paging.paging-morelink{text-align:left}.course_category_tree .paging.paging-morelink a{font-size:11.9px}.dir-rtl .courses>.paging.paging-morelink,.dir-rtl .course_category_tree .paging.paging-morelink{text-align:right}#page-course-index-category .generalbox.info{padding:5px;margin-bottom:15px;border:1px dotted #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}#page-course-index-category .categorypicker{margin:10px 0 20px;text-align:center}.section .activity .moodle-actionmenu .iconsmall{width:16px;width:1rem;height:16px;height:1rem;max-width:none!important;padding:.3em}.filemanager,.filepicker,.file-picker{font-size:11px}.filemanager a,.file-picker a,.filemanager a:hover,.file-picker a:hover{color:#555;text-decoration:none}.filemanager input[type="text"],.file-picker input[type="text"]{width:265px}.fp-content-center{display:table-cell;width:100%;height:100%;vertical-align:middle}.fp-content-hidden{visibility:hidden}.yui3-panel-focused{outline:0}#filesskin .yui3-panel-content{display:inline-block;*display:inline;padding-bottom:20px;background:#f2f2f2;border:1px solid #fff;-webkit-border-radius:8px;-moz-border-radius:8px;border-radius:8px;*zoom:1;-webkit-box-shadow:5px 5px 20px 0 #666;-moz-box-shadow:5px 5px 20px 0 #666;box-shadow:5px 5px 20px 0 #666}#filesskin .yui3-widget-hd{padding:5px;font-size:12px;letter-spacing:1px;color:#333;text-align:center;text-shadow:1px 1px 1px #fff;background-color:#ebebeb;background-image:-moz-linear-gradient(top,#fff,#ccc);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#ccc));background-image:-webkit-linear-gradient(top,#fff,#ccc);background-image:-o-linear-gradient(top,#fff,#ccc);background-image:linear-gradient(to bottom,#fff,#ccc);background-repeat:repeat-x;border-bottom:1px solid #bbb;-webkit-border-radius:10px 10px 0 0;-moz-border-radius:10px 10px 0 0;border-radius:10px 10px 0 0;filter:dropshadow(color=#ffffff,offx=1,offy=1);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffcccccc',GradientType=0)}.fp-panel-button{display:inline-block;*display:inline;padding:3px 20px 2px 20px;margin:10px;text-align:center;background:#fff;-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px;*zoom:1;-webkit-box-shadow:2px 2px 3px .1px #999;-moz-box-shadow:2px 2px 3px .1px #999;box-shadow:2px 2px 3px .1px #999}.filepicker .moodle-dialogue-wrap .moodle-dialogue-bd{padding:0}#filesskin .file-picker.fp-generallayout{position:relative;width:859px;background:#fff;border:1px solid #ccc;-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px}.file-picker .fp-repo-area{display:inline-block;*display:inline;float:left;width:180px;height:525px;overflow:auto;border-right:1px solid #bbb;*zoom:1}.dir-rtl .file-picker .fp-repo-area{float:right;border-right:0;border-left:1px solid #bbb}.file-picker .fp-repo-items{float:left;width:693px}.dir-rtl .file-picker .fp-repo-items{float:right}.file-picker .fp-navbar{min-height:22px;padding:5px 8px;background:#f2f2f2;border-bottom:1px solid #bbb}.file-picker .fp-content{height:468px;overflow:auto;clear:both;background:#fff}.filepicker.moodle-dialogue-fullscreen .file-picker .fp-content{width:100%;height:100%}.dir-rtl .file-picker .fp-repo-items{margin-right:181px}.file-picker .fp-content-loading{display:table;width:100%;height:100%;text-align:center}.file-picker .fp-content .fp-object-container{width:98%;height:98%}.dir-rtl .file-picker .fp-list{text-align:right}.dir-rtl .file-picker .fp-toolbar{padding:0}.dir-rtl .file-picker .fp-list{text-align:right}.dir-rtl .file-picker .fp-repo-name{display:inline}.dir-rtl .file-picker .fp-pathbar{display:block;text-align:right;border-top:0}.dir-rtl .file-picker div.bd{text-align:right}.dir-rtl #filemenu .yuimenuitemlabel{text-align:right}.dir-rtl .filepicker .yui-layout-unit-left{left:500px}.dir-rtl .filepicker .yui-layout-unit-center{left:0}.dir-rtl .filemanager-toolbar a{padding:0}.file-picker .fp-list{float:left;width:100%;padding:0;margin:0;list-style-type:none}.dir-rtl .file-picker .fp-list{float:left;text-align:right}.file-picker .fp-list .fp-repo a{display:block;padding:.5em .7em}.file-picker .fp-list .fp-repo.active{background:#f2f2f2}.file-picker .fp-list .fp-repo-icon{padding:0 7px 0 5px}.fp-toolbar{display:table-row;float:left;max-width:70%;line-height:22px}.dir-rtl .fp-toolbar{float:right}.fp-toolbar.empty{display:none}.fp-toolbar .disabled{display:none}.fp-toolbar div{display:inline-block;*display:inline;padding:0 2px;padding-right:10px;*zoom:1}.dir-rtl .fp-toolbar div{width:100px}.fp-toolbar img{margin-right:5px;vertical-align:-15%}.fp-toolbar .fp-tb-search{width:228px;height:14px}.fp-toolbar .fp-tb-search input{width:200px;height:16px;padding:2px 6px 1px 20px;background:#fff url('[[pix:a/search]]') no-repeat 3px 3px;border:1px solid #bbb}.fp-viewbar{float:right;width:69px;height:22px;margin-right:8px}.dir-rtl .fp-toolbar img{vertical-align:-35%}.dir-rtl .fp-viewbar{float:left;width:100px}.fp-vb-icons{display:inline-block;*display:inline;width:22px;height:22px;background:url('[[pix:theme|fp/view_icon_active]]') no-repeat 0 0;*zoom:1}.dir-rtl .fp-vb-icons{display:block;float:left;margin-right:4px;background:url('[[pix:theme|fp/view_icon_active]]') no-repeat 0 0}.fp-vb-icons.checked{background:url('[[pix:theme|fp/view_icon_selected]]')}.dir-rtl .fp-vb-icons.checked{display:block;float:left;margin-right:4px;background:url('[[pix:theme|fp/view_icon_selected]]')}.fp-viewbar.disabled .fp-vb-icons{background:url('[[pix:theme|fp/view_icon_inactive]]')}.fp-vb-details{display:inline-block;*display:inline;width:23px;height:22px;margin-left:-4px;background:url('[[pix:theme|fp/view_list_active]]') no-repeat 0 0;*zoom:1}.dir-rtl .fp-vb-details{display:block;float:left;margin-right:4px;background:url('[[pix:theme|fp/view_list_active]]') no-repeat 0 0}.fp-vb-details.checked{background:url('[[pix:theme|fp/view_list_selected]]')}.dir