Merge branch '44354-27' of git://github.com/samhemelryk/moodle
authorDan Poltawski <dan@moodle.com>
Tue, 15 Apr 2014 02:26:06 +0000 (10:26 +0800)
committerDan Poltawski <dan@moodle.com>
Tue, 15 Apr 2014 02:26:06 +0000 (10:26 +0800)
126 files changed:
admin/tool/log/db/install.php
admin/tool/log/db/upgrade.php [new file with mode: 0644]
admin/tool/log/store/legacy/settings.php
admin/tool/log/version.php
availability/classes/info_section.php
availability/tests/behat/edit_availability.feature
filter/glossary/filter.php
filter/glossary/tests/filter_test.php
filter/glossary/version.php
filter/tex/filter.php
filter/tex/tests/filter_test.php [new file with mode: 0644]
install/lang/de/admin.php
install/lang/en_us/admin.php [new file with mode: 0644]
install/lang/it/install.php
install/lang/kl/moodle.php [new file with mode: 0644]
install/lang/lt/error.php [new file with mode: 0644]
install/lang/mn_mong/langconfig.php [new file with mode: 0644]
install/lang/no/langconfig.php
lang/en/moodle.php
lang/en/question.php
lib/classes/event/base.php
lib/classes/event/question_category_created.php [new file with mode: 0644]
lib/classes/event/unknown_logged.php [new file with mode: 0644]
lib/csslib.php
lib/db/services.php
lib/editor/atto/plugins/table/yui/build/moodle-atto_table-button/moodle-atto_table-button-debug.js
lib/editor/atto/plugins/table/yui/build/moodle-atto_table-button/moodle-atto_table-button-min.js
lib/editor/atto/plugins/table/yui/build/moodle-atto_table-button/moodle-atto_table-button.js
lib/editor/atto/plugins/table/yui/src/button/js/button.js
lib/editor/atto/styles.css
lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-debug.js
lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-min.js
lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor.js
lib/editor/atto/yui/build/moodle-editor_atto-plugin/moodle-editor_atto-plugin-debug.js
lib/editor/atto/yui/build/moodle-editor_atto-plugin/moodle-editor_atto-plugin-min.js
lib/editor/atto/yui/build/moodle-editor_atto-plugin/moodle-editor_atto-plugin.js
lib/editor/atto/yui/src/editor/js/editor-plugin-buttons.js
lib/editor/atto/yui/src/editor/js/toolbar-keyboardnav.js
lib/modinfolib.php
lib/setuplib.php
lib/tests/csslib_test.php
lib/tests/event_unknown_logged_test.php [new file with mode: 0644]
message/output/airnotifier/classes/manager.php [changed mode: 0755->0644]
message/output/airnotifier/db/access.php [changed mode: 0755->0644]
message/output/airnotifier/db/install.php [changed mode: 0755->0644]
message/output/airnotifier/db/install.xml [changed mode: 0755->0644]
message/output/airnotifier/db/services.php [changed mode: 0755->0644]
message/output/airnotifier/externallib.php [changed mode: 0755->0644]
message/output/airnotifier/lang/en/message_airnotifier.php [changed mode: 0755->0644]
message/output/airnotifier/message_output_airnotifier.php [changed mode: 0755->0644]
message/output/airnotifier/requestaccesskey.php [changed mode: 0755->0644]
message/output/airnotifier/rest.php [changed mode: 0755->0644]
message/output/airnotifier/settings.php [changed mode: 0755->0644]
message/output/airnotifier/style.css [changed mode: 0755->0644]
message/output/airnotifier/tests/externallib_test.php [changed mode: 0755->0644]
message/output/airnotifier/version.php [changed mode: 0755->0644]
message/output/airnotifier/yui/src/toolboxes/js/toolboxes.js [changed mode: 0755->0644]
mod/glossary/approve.php
mod/glossary/classes/local/concept_cache.php [new file with mode: 0644]
mod/glossary/db/caches.php [new file with mode: 0644]
mod/glossary/db/events.php [new file with mode: 0644]
mod/glossary/deleteentry.php
mod/glossary/edit.php
mod/glossary/editcategories.php
mod/glossary/import.php
mod/glossary/lang/en/glossary.php
mod/glossary/lib.php
mod/glossary/tests/concept_cache_test.php [new file with mode: 0644]
mod/glossary/tests/generator/lib.php
mod/glossary/tests/generator_test.php
mod/glossary/version.php
mod/lti/lib.php
mod/lti/mod_form.js
mod/lti/mod_form.php
mod/quiz/addrandom.php
mod/quiz/attempt.php
mod/quiz/classes/event/attempt_deleted.php [new file with mode: 0644]
mod/quiz/classes/event/attempt_preview_started.php [new file with mode: 0644]
mod/quiz/classes/event/attempt_reviewed.php [new file with mode: 0644]
mod/quiz/classes/event/attempt_started.php
mod/quiz/classes/event/attempt_summary_viewed.php [new file with mode: 0644]
mod/quiz/classes/event/attempt_viewed.php [new file with mode: 0644]
mod/quiz/classes/event/course_module_instance_list_viewed.php [new file with mode: 0644]
mod/quiz/classes/event/course_module_viewed.php [new file with mode: 0644]
mod/quiz/classes/event/edit_page_viewed.php [new file with mode: 0644]
mod/quiz/classes/event/group_override_created.php [new file with mode: 0644]
mod/quiz/classes/event/group_override_deleted.php [new file with mode: 0644]
mod/quiz/classes/event/group_override_updated.php [new file with mode: 0644]
mod/quiz/classes/event/question_manually_graded.php [new file with mode: 0644]
mod/quiz/classes/event/report_viewed.php [new file with mode: 0644]
mod/quiz/classes/event/user_override_created.php [new file with mode: 0644]
mod/quiz/classes/event/user_override_deleted.php [new file with mode: 0644]
mod/quiz/classes/event/user_override_updated.php [new file with mode: 0644]
mod/quiz/comment.php
mod/quiz/edit.php
mod/quiz/index.php
mod/quiz/lang/en/quiz.php
mod/quiz/lib.php
mod/quiz/locallib.php
mod/quiz/overridedelete.php
mod/quiz/overrideedit.php
mod/quiz/processattempt.php
mod/quiz/report.php
mod/quiz/report/attemptsreport.php
mod/quiz/report/statistics/tests/fixtures/questions03.csv [new file with mode: 0644]
mod/quiz/report/statistics/tests/fixtures/quizzes.csv
mod/quiz/report/statistics/tests/fixtures/responsecounts00.csv
mod/quiz/report/statistics/tests/fixtures/responsecounts03.csv [new file with mode: 0644]
mod/quiz/report/statistics/tests/fixtures/steps03.csv [new file with mode: 0644]
mod/quiz/review.php
mod/quiz/summary.php
mod/quiz/tests/events_test.php
mod/quiz/view.php
mod/upgrade.txt
question/category_class.php
question/classes/statistics/questions/all_calculated_for_qubaid_condition.php
question/classes/statistics/questions/calculated.php
question/classes/statistics/questions/calculator.php
question/classes/statistics/responses/analysis_for_class.php
question/tests/events_test.php [new file with mode: 0644]
question/type/numerical/question.php
repository/wikimedia/lib.php
tag/manage.php
theme/bootstrapbase/less/moodle/core.less
theme/bootstrapbase/style/moodle.css
version.php

index 6fccec4..52d2d9d 100644 (file)
 
 defined('MOODLE_INTERNAL') || die();
 
+/**
+ * Install the plugin.
+ */
 function xmldb_tool_log_install() {
-    global $CFG;
+    global $CFG, $DB;
 
     $enabled = array();
 
-    // For now enable only the legacy logging, this keeps 100% BC.
+    // Add data to new log only from now on.
+    if (file_exists("$CFG->dirroot/$CFG->admin/tool/log/store/standard")) {
+        $enabled[] = 'logstore_standard';
+    }
+
+    // Enable legacy log reading, but only if there are existing data.
     if (file_exists("$CFG->dirroot/$CFG->admin/tool/log/store/legacy")) {
-        $enabled[] = 'logstore_legacy';
+        unset_config('loglegacy', 'logstore_legacy');
+        // Do not enabled legacy logging if somebody installed a new
+        // site and in less than one day upgraded to 2.7.
+        $params = array('yesterday' => time() - 60*60*24);
+        if ($DB->record_exists_select('log', "time < :yesterday", $params)) {
+            $enabled[] = 'logstore_legacy';
+        }
     }
 
     set_config('enabled_stores', implode(',', $enabled), 'tool_log');
diff --git a/admin/tool/log/db/upgrade.php b/admin/tool/log/db/upgrade.php
new file mode 100644 (file)
index 0000000..0dc0597
--- /dev/null
@@ -0,0 +1,47 @@
+<?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/>.
+
+/**
+ * Logging support.
+ *
+ * @package    tool_log
+ * @copyright  2014 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Upgrade the plugin.
+ *
+ * @param int $oldversion
+ * @return bool always true
+ */
+function xmldb_tool_log_upgrade($oldversion) {
+    global $CFG, $DB, $OUTPUT;
+
+    $dbman = $DB->get_manager();
+
+    if ($oldversion < 2014040600) {
+        // Reset logging defaults in dev branches,
+        // in production upgrade the install.php is executed instead.
+        require_once(__DIR__ . '/install.php');
+        xmldb_tool_log_install();
+        upgrade_plugin_savepoint(true, 2014040600, 'tool', 'log');
+    }
+
+    return true;
+}
index d9ed97b..0a7149e 100644 (file)
@@ -27,7 +27,7 @@ defined('MOODLE_INTERNAL') || die();
 if ($hassiteconfig) {
     $settings->add(new admin_setting_configcheckbox('logstore_legacy/loglegacy',
         new lang_string('loglegacy', 'logstore_legacy'),
-        new lang_string('loglegacy_help', 'logstore_legacy'), 1));
+        new lang_string('loglegacy_help', 'logstore_legacy'), 0));
 
     $settings->add(new admin_setting_configcheckbox('logguests',
         new lang_string('logguests', 'admin'),
index 0838cd2..a70afc3 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version = 2014011300; // The current plugin version (Date: YYYYMMDDXX).
-$plugin->requires = 2014011000; // Requires this Moodle version.
+$plugin->version = 2014040600; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->requires = 2014040300; // Requires this Moodle version.
 $plugin->component = 'tool_log'; // Full name of the plugin (used for diagnostics).
index 67f02d6..777c46a 100644 (file)
@@ -43,7 +43,7 @@ class info_section extends info {
      * @param \section_info $section Section object
      */
     public function __construct(\section_info $section) {
-        parent::__construct(\get_course($section->course), $section->visible,
+        parent::__construct($section->modinfo->get_course(), $section->visible,
                 $section->availability);
         $this->section = $section;
     }
index 254b37e..ca8e859 100644 (file)
@@ -1,4 +1,4 @@
-@core @core_availability @wip
+@core @core_availability
 Feature: edit_availability
   In order to control which students can see activities
   As a teacher
@@ -17,14 +17,6 @@ Feature: edit_availability
       | teacher1 | C1     | editingteacher |
       | student1 | C1     | student        |
 
-  # This scenario does not need JavaScript, but I have added the tag because
-  # after today's composer/package update, I get this error:
-  # Notice: Undefined index: host in
-  # [...]\vendor\symfony\browser-kit\Symfony\Component\BrowserKit\CookieJar.php
-  # line 217
-  # Adding @javascript here causes it not to use whatever they just broke, and
-  # it then works again.
-  @javascript
   Scenario: Confirm the 'enable availability' option is working
     When I log in as "teacher1"
     And I follow "Course 1"
index a1ddebb..016efd6 100644 (file)
@@ -17,7 +17,7 @@
 /**
  * This filter provides automatic linking to
  * glossary entries, aliases and categories when
- * found inside every Moodle text
+ * found inside every Moodle text.
  *
  * @package    filter
  * @subpackage glossary
 defined('MOODLE_INTERNAL') || die();
 
 /**
- * Glossary filtering
+ * Glossary linking filter class.
  *
- * TODO: erase the $GLOSSARY_EXCLUDECONCEPTS global => require format_text()
- *       to be able to pass arbitrary $options['filteroptions']['glossary'] to filter_text()
+ * NOTE: multilang glossary entries are not compatible with this filter.
  */
 class filter_glossary extends moodle_text_filter {
+    /** @var int $cachecourseid cache invalidation flag in case content from multiple courses displayed. */
+    protected $cachecourseid = null;
+    /** @var int $cacheuserid cache invalidation flag in case user is switched. */
+    protected $cacheuserid = null;
+    /** @var array $cacheconceptlist page level filter cache, this should be always faster than MUC */
+    protected $cacheconceptlist = null;
 
     public function setup($page, $context) {
         // This only requires execution once per request.
@@ -49,185 +54,100 @@ class filter_glossary extends moodle_text_filter {
     }
 
     public function filter($text, array $options = array()) {
-        global $CFG, $DB, $GLOSSARY_EXCLUDECONCEPTS;
-
-        // Trivial-cache - keyed on $cachedcontextid
-        static $cachedcontextid;
-        static $conceptlist;
-
-        static $nothingtodo;         // To avoid processing if no glossaries / concepts are found
+        global $CFG, $USER, $GLOSSARY_EXCLUDEENTRY;
 
         // Try to get current course.
         $coursectx = $this->context->get_course_context(false);
         if (!$coursectx) {
+            // Only global glossaries will be linked.
             $courseid = 0;
         } else {
             $courseid = $coursectx->instanceid;
         }
 
-        // Initialise/invalidate our trivial cache if dealing with a different context
-        if (!isset($cachedcontextid) || $cachedcontextid !== $this->context->id) {
-            $cachedcontextid = $this->context->id;
-            $conceptlist = array();
-            $nothingtodo = false;
-        }
-
-        if (($nothingtodo === true) || (!has_capability('mod/glossary:view', $this->context))) {
-            return $text;
+        if ($this->cachecourseid != $courseid or $this->cacheuserid != $USER->id) {
+            // Invalidate the page cache.
+            $this->cacheconceptlist = null;
         }
 
-        // Create a list of all the concepts to search for.  It may be cached already.
-        if (empty($conceptlist)) {
-
-            // Find all the glossaries we need to examine
-            if (!$glossaries = $DB->get_records_sql_menu('
-                    SELECT g.id, g.name
-                      FROM {glossary} g, {course_modules} cm, {modules} m
-                     WHERE m.name = \'glossary\'
-                       AND cm.module = m.id
-                       AND cm.visible = 1
-                       AND g.id = cm.instance
-                       AND g.usedynalink != 0
-                       AND (g.course = ? OR g.globalglossary = 1)
-                  ORDER BY g.globalglossary, g.id', array($courseid))) {
-                $nothingtodo = true;
+        if (is_array($this->cacheconceptlist) and empty($GLOSSARY_EXCLUDEENTRY)) {
+            if (empty($this->cacheconceptlist)) {
                 return $text;
             }
+            return filter_phrases($text, $this->cacheconceptlist);
+        }
 
-            // Make a list of glossary IDs for searching
-            $glossarylist = implode(',', array_keys($glossaries));
-
-            // Pull out all the raw data from the database for entries, categories and aliases
-            $entries = $DB->get_records_select('glossary_entries',
-                    'glossaryid IN ('.$glossarylist.') AND usedynalink != 0 AND approved != 0 ', null, '',
-                    'id,glossaryid, concept, casesensitive, 0 AS category, fullmatch');
-
-            $categories = $DB->get_records_select('glossary_categories',
-                    'glossaryid IN ('.$glossarylist.') AND usedynalink != 0', null, '',
-                    'id,glossaryid,name AS concept, 1 AS casesensitive, 1 AS category, 1 AS fullmatch');
-
-            $aliases = $DB->get_records_sql('
-                    SELECT ga.id, ge.id AS entryid, ge.glossaryid,
-                           ga.alias AS concept, ge.concept AS originalconcept,
-                           casesensitive, 0 AS category, fullmatch
-                      FROM {glossary_alias} ga,
-                           {glossary_entries} ge
-                      WHERE ga.entryid = ge.id
-                        AND ge.glossaryid IN ('.$glossarylist.')
-                        AND ge.usedynalink != 0
-                        AND ge.approved != 0', null);
-
-            // Combine them into one big list
-            $concepts = array();
-            if ($entries and $categories) {
-                $concepts = array_merge($entries, $categories);
-            } else if ($categories) {
-                $concepts = $categories;
-            } else if ($entries) {
-                $concepts = $entries;
-            }
-
-            if ($aliases) {
-                $concepts = array_merge($concepts, $aliases);
-            }
-
-            if (!empty($concepts)) {
-                foreach ($concepts as $key => $concept) {
-                    // Trim empty or unlinkable concepts
-                    $currentconcept = trim(strip_tags($concept->concept));
-
-                    // Concept must be HTML-escaped, so do the same as format_string
-                    // to turn ampersands into &amp;.
-                    $currentconcept = replace_ampersands_not_followed_by_entity($currentconcept);
-
-                    if (empty($currentconcept)) {
-                        unset($concepts[$key]);
-                        continue;
-                    } else {
-                        $concepts[$key]->concept = $currentconcept;
-                    }
-
-                    // Rule out any small integers.  See bug 1446
-                    $currentint = intval($currentconcept);
-                    if ($currentint && (strval($currentint) == $currentconcept) && $currentint < 1000) {
-                        unset($concepts[$key]);
-                    }
-                }
-            }
-
-            if (empty($concepts)) {
-                $nothingtodo = true;
-                return $text;
-            }
+        list($glossaries, $allconcepts) = \mod_glossary\local\concept_cache::get_concepts($courseid);
 
-            usort($concepts, 'filter_glossary::sort_entries_by_length');
+        if (!$allconcepts) {
+            $this->cacheuserid = $USER->id;
+            $this->cachecourseid = $courseid;
+            $this->cacheconcepts = array();
+            return $text;
+        }
 
-            $strcategory = get_string('category', 'glossary');
+        $strcategory = get_string('category', 'glossary');
 
-            // Loop through all the concepts, setting up our data structure for the filter
-            $conceptlist = array();    // We will store all the concepts here
+        $conceptlist = array();
+        $excluded = false;
 
+        foreach ($allconcepts as $concepts) {
             foreach ($concepts as $concept) {
-                $glossaryname = str_replace(':', '-', $glossaries[$concept->glossaryid]);
-                if ($concept->category) {       // Link to a category
-                    // TODO: Fix this string usage
-                    $title = strip_tags($glossaryname.': '.$strcategory.' '.$concept->concept);
-                    $href_tag_begin = '<a class="glossary autolink category glossaryid'.$concept->glossaryid.'" title="'.$title.'" '.
-                                      'href="'.$CFG->wwwroot.'/mod/glossary/view.php?g='.$concept->glossaryid.
-                                      '&amp;mode=cat&amp;hook='.$concept->id.'">';
+                if (!empty($GLOSSARY_EXCLUDEENTRY) and $concept->id == $GLOSSARY_EXCLUDEENTRY) {
+                    $excluded = true;
+                    continue;
+                }
+                if ($concept->category) { // Link to a category.
+                    // TODO: Fix this string usage.
+                    $title = $glossaries[$concept->glossaryid] . ': ' . $strcategory . ' ' . $concept->concept;
+                    $link = new moodle_url('/mod/glossary/view.php', array('g' => $concept->glossaryid, 'mode' => 'cat', 'hook' => $concept->id));
+                    $attributes = array(
+                        'href'  => $link,
+                        'title' => $title,
+                        'class' => 'glossary autolink category glossaryid' . $concept->glossaryid);
+
                 } else { // Link to entry or alias
-                    if (!empty($concept->originalconcept)) {  // We are dealing with an alias (so show and point to original)
-                        $title = str_replace('"', "'", html_entity_decode(
-                                strip_tags($glossaryname.': '.$concept->originalconcept)));
-                        $concept->id = $concept->entryid;
-                    } else { // This is an entry
-                        // We need to remove entities from the content here because it
-                        // will be escaped by html_writer below.
-                        $title = str_replace('"', "'", html_entity_decode(
-                                strip_tags($glossaryname.': '.$concept->concept)));
-                    }
-                    // hardcoding dictionary format in the URL rather than defaulting
+                    $title = $glossaries[$concept->glossaryid] . ': ' . $concept->concept;
+                    // Hardcoding dictionary format in the URL rather than defaulting
                     // to the current glossary format which may not work in a popup.
                     // for example "entry list" means the popup would only contain
                     // a link that opens another popup.
-                    $link = new moodle_url('/mod/glossary/showentry.php', array('courseid'=>$courseid, 'eid'=>$concept->id, 'displayformat'=>'dictionary'));
+                    $link = new moodle_url('/mod/glossary/showentry.php', array('eid' => $concept->id, 'displayformat' => 'dictionary'));
                     $attributes = array(
-                        'href' => $link,
-                        'title'=> $title,
-                        'class'=> 'glossary autolink concept glossaryid'.$concept->glossaryid);
-
-                    // this flag is optionally set by resource_pluginfile()
-                    // if processing an embedded file use target to prevent getting nested Moodles
-                    if (isset($CFG->embeddedsoforcelinktarget) && $CFG->embeddedsoforcelinktarget) {
-                        $attributes['target'] = '_top';
-                    }
-
-                    $href_tag_begin = html_writer::start_tag('a', $attributes);
+                        'href'  => $link,
+                        'title' => str_replace('&amp;', '&', $title), // Undo the s() mangling.
+                        'class' => 'glossary autolink concept glossaryid' . $concept->glossaryid);
                 }
+                // This flag is optionally set by resource_pluginfile()
+                // if processing an embedded file use target to prevent getting nested Moodles.
+                if (!empty($CFG->embeddedsoforcelinktarget)) {
+                    $attributes['target'] = '_top';
+                }
+                $href_tag_begin = html_writer::start_tag('a', $attributes);
+
                 $conceptlist[] = new filterobject($concept->concept, $href_tag_begin, '</a>',
                     $concept->casesensitive, $concept->fullmatch);
             }
-
-            $conceptlist = filter_remove_duplicates($conceptlist);
         }
 
-        if (!empty($GLOSSARY_EXCLUDECONCEPTS)) {
-            $reducedconceptlist=array();
-            foreach($conceptlist as $concept) {
-                if(!in_array($concept->phrase,$GLOSSARY_EXCLUDECONCEPTS)) {
-                    $reducedconceptlist[]=$concept;
-                }
-            }
-            return filter_phrases($text, $reducedconceptlist);
+        usort($conceptlist, 'filter_glossary::sort_entries_by_length');
+
+        if (!$excluded) {
+            // Do not cache the excluded list here, it is used once per page only.
+            $this->cacheuserid = $USER->id;
+            $this->cachecourseid = $courseid;
+            $this->cacheconceptlist = $conceptlist;
         }
 
+        if (empty($conceptlist)) {
+            return $text;
+        }
         return filter_phrases($text, $conceptlist);   // Actually search for concepts!
     }
 
-
     private static function sort_entries_by_length($entry0, $entry1) {
-        $len0 = strlen($entry0->concept);
-        $len1 = strlen($entry1->concept);
+        $len0 = strlen($entry0->phrase);
+        $len1 = strlen($entry1->phrase);
 
         if ($len0 < $len1) {
             return 1;
index c8f3966..ccf54ed 100644 (file)
@@ -53,18 +53,22 @@ class filter_glossary_filter_testcase extends advanced_testcase {
                 array('course' => $course->id, 'mainglossary' => 1));
 
         // Create two entries with ampersands and one normal entry.
+        /** @var mod_glossary_generator $generator */
         $generator = $this->getDataGenerator()->get_plugin_generator('mod_glossary');
         $normal = $generator->create_content($glossary, array('concept' => 'normal'));
         $amp1 = $generator->create_content($glossary, array('concept' => 'A&B'));
         $amp2 = $generator->create_content($glossary, array('concept' => 'C&amp;D'));
 
+        filter_manager::reset_caches();
+        \mod_glossary\local\concept_cache::reset_caches();
+
         // Format text with all three entries in HTML.
         $html = '<p>A&amp;B C&amp;D normal</p>';
         $filtered = format_text($html, FORMAT_HTML, array('context' => $context));
 
         // Find all the glossary links in the result.
         $matches = array();
-        preg_match_all('~courseid=' . $course->id . '&amp;eid=([0-9]+).*?title="(.*?)"~', $filtered, $matches);
+        preg_match_all('~eid=([0-9]+).*?title="(.*?)"~', $filtered, $matches);
 
         // There should be 3 glossary links.
         $this->assertEquals(3, count($matches[1]));
index 9d099fb..d20d57f 100644 (file)
@@ -25,8 +25,8 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version  = 2013110500;
-$plugin->requires = 2013110500;  // Requires this Moodle version
+$plugin->version  = 2014040600;
+$plugin->requires = 2014040300;  // Requires this Moodle version
 $plugin->component= 'filter_glossary';
 
-$plugin->dependencies = array('mod_glossary' => 2013110500);
+$plugin->dependencies = array('mod_glossary' => 2014040600);
index 6e593f3..05eae5d 100644 (file)
@@ -114,7 +114,11 @@ class filter_tex extends moodle_text_filter {
         global $CFG, $DB;
 
         /// Do a quick check using stripos to avoid unnecessary work
-        if (!preg_match('/<tex/i',$text) and !strstr($text,'$$') and !strstr($text,'\\[') and !preg_match('/\[tex/i',$text)) { //added one more tag (dlnsk)
+        if ((!preg_match('/<tex/i', $text)) &&
+                (strpos($text,'$$') === false) &&
+                (strpos($text,'\\[') === false) &&
+                (strpos($text, '\\(') === false) &&
+                (!preg_match('/\[tex/i',$text))) {
             return $text;
         }
 
@@ -146,9 +150,20 @@ class filter_tex extends moodle_text_filter {
         // or $$ TeX expression $$
         // or \[ TeX expression \]          // original tag of MathType and TeXaide (dlnsk)
         // or [tex] TeX expression [/tex]   // somtime it's more comfortable than <tex> (dlnsk)
-        preg_match_all('/<tex(?:\s+alt=["\'](.*?)["\'])?>(.+?)<\/tex>|\$\$(.+?)\$\$|\\\\\[(.+?)\\\\\]|\\[tex\\](.+?)\\[\/tex\\]/is', $text, $matches);
+        $rules = array(
+            '<tex(?:\s+alt=["\'](.*?)["\'])?>(.+?)<\/tex>',
+            '\$\$(.+?)\$\$',
+            '\\\\\[(.+?)\\\\\]',
+            '\\\\\((.+?)\\\\\)',
+            '\\[tex\\](.+?)\\[\/tex\\]'
+        );
+        $megarule = '/' . implode($rules, '|') . '/is';
+        preg_match_all($megarule, $text, $matches);
         for ($i=0; $i<count($matches[0]); $i++) {
-            $texexp = $matches[2][$i] . $matches[3][$i] . $matches[4][$i] . $matches[5][$i];
+            $texexp = '';
+            for ($j = 0; $j < count($rules); $j++) {
+                $texexp .= $matches[$j + 2][$i];
+            }
             $alt = $matches[1][$i];
             $texexp = str_replace('<nolink>','',$texexp);
             $texexp = str_replace('</nolink>','',$texexp);
diff --git a/filter/tex/tests/filter_test.php b/filter/tex/tests/filter_test.php
new file mode 100644 (file)
index 0000000..1b03725
--- /dev/null
@@ -0,0 +1,81 @@
+<?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 test for the filter_tex
+ *
+ * @package    filter_tex
+ * @copyright  2014 Damyon Wiese
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->dirroot . '/filter/tex/filter.php');
+
+
+/**
+ * Unit tests for filter_tex.
+ *
+ * Test the delimiter parsing used by the tex filter.
+ *
+ * @copyright  2014 Damyon Wiese
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class filter_tex_testcase extends advanced_testcase {
+
+    protected $filter;
+
+    protected function setUp() {
+        parent::setUp();
+        $this->resetAfterTest(true);
+        $this->filter = new filter_tex(context_system::instance(), array());
+    }
+
+    function run_with_delimiters($start, $end, $filtershouldrun) {
+        $pre = 'Some pre text';
+        $post = 'Some post text';
+        $equation = ' \sum{a^b} ';
+
+        $before = $pre . $start . $equation . $end . $post;
+
+        $after = trim($this->filter->filter($before));
+
+        if ($filtershouldrun) {
+            $this->assertNotEquals($after, $before);
+        } else {
+            $this->assertEquals($after, $before);
+        }
+    }
+
+    function test_delimiters() {
+        // First test the list of supported delimiters.
+        $this->run_with_delimiters('$$', '$$', true);
+        $this->run_with_delimiters('\\(', '\\)', true);
+        $this->run_with_delimiters('\\[', '\\]', true);
+        $this->run_with_delimiters('[tex]', '[/tex]', true);
+        $this->run_with_delimiters('<tex>', '</tex>', true);
+        $this->run_with_delimiters('<tex alt="nonsense">', '</tex>', true);
+        // Now test some cases that shouldn't be executed.
+        $this->run_with_delimiters('<textarea>', '</textarea>', false);
+        $this->run_with_delimiters('$', '$', false);
+        $this->run_with_delimiters('(', ')', false);
+        $this->run_with_delimiters('[', ']', false);
+        $this->run_with_delimiters('$$', '\\]', false);
+    }
+
+}
index 7a9a9fe..2d58506 100644 (file)
@@ -38,7 +38,7 @@ $string['clitypevalue'] = 'Wert eingeben';
 $string['clitypevaluedefault'] = 'Wert eingeben oder Standardwert benutzen ({$a})';
 $string['cliunknowoption'] = 'Nicht erkannte Optionen:
   {$a}
-Hilfe wird über die Option -help angezeigt.';
+Hilfe wird über die Option --help angezeigt.';
 $string['cliyesnoprompt'] = 'y (yes=ja) oder n (no=nein) eingeben';
 $string['environmentrequireinstall'] = 'muss installiert und aktiviert sein';
 $string['environmentrequireversion'] = 'Version {$a->needed} ist erforderlich - aktuell ist {$a->current} installiert.';
diff --git a/install/lang/en_us/admin.php b/install/lang/en_us/admin.php
new file mode 100644 (file)
index 0000000..e321f9d
--- /dev/null
@@ -0,0 +1,35 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Automatically generated strings for Moodle installer
+ *
+ * Do not edit this file manually! It contains just a subset of strings
+ * needed during the very first steps of installation. This file was
+ * generated automatically by export-installer.php (which is part of AMOS
+ * {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
+ * list of strings defined in /install/stringnames.txt.
+ *
+ * @package   installer
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$string['cliunknowoption'] = 'Unrecognized options:
+  {$a}
+Please use --help option.';
index 3a83b60..a1db518 100644 (file)
@@ -46,7 +46,7 @@ $string['dbprefix'] = 'Prefisso tabelle';
 $string['dirroot'] = 'Cartella di Moodle';
 $string['environmenthead'] = 'Verifica dell\'ambiente...';
 $string['environmentsub2'] = 'Ciascuna release di Moodle prevede come requisito minimo una data versione del PHP ed una serie di estensioni. Prima di una installazione o di un aggiornamento viene eseguita la verifica dei requisiti minimi. Se non sai come installare nuove versioni del PHP o le sue estensioni, contatta l\'amministratore del tuo server.';
-$string['errorsinenvironment'] = 'Ci sono problemi nel vostro ambiente';
+$string['errorsinenvironment'] = 'Sono stati riscontarti problemi nel tuo ambiente';
 $string['installation'] = 'Installazione';
 $string['langdownloaderror'] = 'Purtroppo non è stato possibile scaricare la lingua "{$a}". L\'installazione proseguirà in lingua Inglese.';
 $string['memorylimithelp'] = '<p>Il limite di memoria assegnata al PHP attualmente è {$a}.</p>
@@ -57,10 +57,10 @@ $string['memorylimithelp'] = '<p>Il limite di memoria assegnata al PHP attualmen
 <ol>
 <li>Se possibile, ricompila il PHP con l\'opzione <i>--enable-memory-limit</i>.
 Questo consentirà a Moodle di impostare in autonomia il limite di memoria.</li>
-<li>Se hai accesso al file php.ini, è possibile modificare la variabile <b>memory_limit</b> a un valore più alto, ad esempio 40M. Se non hai accesso, potete chiedere al vostro amministratore di sistema di farlo.</li>
+<li>Se hai accesso al file php.ini, è possibile modificare la variabile <b>memory_limit</b> a un valore più alto, ad esempio 40M. Se non hai accesso, puoi chiedere all\'amministratore di sistema di farlo.</li>
 <li>Su alcuni server con il PHP è possibile creare un file .htaccess nella cartella di Moodle contenente questa linea:
 <blockquote>php_value memory_limit 40M</blockquote>
-<p>Tuttavia, su alcuni server la direttiva potrebbe impedire  a <b>tutte</b> le pagine PHP di funzionare (apapriranno degli erorri durante la visualizzazione delle pagine), in tal caso dovrai rimuovere il file .htaccess.</li></ol>';
+<p>Tuttavia, su alcuni server la direttiva potrebbe impedire a <b>tutte</b> le pagine PHP di funzionare (appariranno degli errori durante la visualizzazione delle pagine), in tal caso dovrai rimuovere il file .htaccess.</li></ol>';
 $string['paths'] = 'Percorsi';
 $string['pathserrcreatedataroot'] = 'Lo script di installazione non ha potuto creare la Cartella dei dati ({$a->dataroot}).';
 $string['pathshead'] = 'Conferma percorsi';
diff --git a/install/lang/kl/moodle.php b/install/lang/kl/moodle.php
new file mode 100644 (file)
index 0000000..35e5a8a
--- /dev/null
@@ -0,0 +1,34 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Automatically generated strings for Moodle installer
+ *
+ * Do not edit this file manually! It contains just a subset of strings
+ * needed during the very first steps of installation. This file was
+ * generated automatically by export-installer.php (which is part of AMOS
+ * {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
+ * list of strings defined in /install/stringnames.txt.
+ *
+ * @package   installer
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$string['language'] = 'Oqaaseq';
+$string['next'] = 'Tullia';
diff --git a/install/lang/lt/error.php b/install/lang/lt/error.php
new file mode 100644 (file)
index 0000000..db7d451
--- /dev/null
@@ -0,0 +1,36 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Automatically generated strings for Moodle installer
+ *
+ * Do not edit this file manually! It contains just a subset of strings
+ * needed during the very first steps of installation. This file was
+ * generated automatically by export-installer.php (which is part of AMOS
+ * {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
+ * list of strings defined in /install/stringnames.txt.
+ *
+ * @package   installer
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$string['cannotcreatelangdir'] = 'Negalima sukurti kalbos katalogo';
+$string['cannotcreatetempdir'] = 'Negalima sukurti laikinojo katalogo';
+$string['cannotdownloadcomponents'] = 'Negalima atsisiųsti komponentų';
+$string['cannotdownloadzipfile'] = 'Negalima atsisiųsti ZIP failo';
diff --git a/install/lang/mn_mong/langconfig.php b/install/lang/mn_mong/langconfig.php
new file mode 100644 (file)
index 0000000..7b9131a
--- /dev/null
@@ -0,0 +1,33 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Automatically generated strings for Moodle installer
+ *
+ * Do not edit this file manually! It contains just a subset of strings
+ * needed during the very first steps of installation. This file was
+ * generated automatically by export-installer.php (which is part of AMOS
+ * {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
+ * list of strings defined in /install/stringnames.txt.
+ *
+ * @package   installer
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$string['thislanguage'] = 'Mongolian';
index c3103ca..91a63b4 100644 (file)
@@ -30,6 +30,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$string['parentlanguage'] = 'Arved språk';
+$string['parentlanguage'] = '';
 $string['thisdirection'] = 'ltr';
 $string['thislanguage'] = 'Norsk - bokmål';
index 916abb7..d09245d 100644 (file)
@@ -739,6 +739,7 @@ $string['eventcoursesectionupdated'] = ' Course section updated';
 $string['eventcoursemoduleinstancelistviewed'] = 'Course module instance list viewed';
 $string['eventemailfailed'] = 'Email failed to send';
 $string['eventname'] = 'Event name';
+$string['eventunknownlogged'] = 'Unknown event';
 $string['eventusercreated'] = 'User created';
 $string['eventuserdeleted'] = 'User deleted';
 $string['eventuserlistviewed'] = 'User list viewed';
index b1f6385..63ef294 100644 (file)
@@ -137,6 +137,7 @@ $string['errorprocess'] = 'Error occurred during processing!';
 $string['errorprocessingresponses'] = 'An error occurred while processing your responses ({$a}). Click continue to return to the page you were on and try again.';
 $string['errorsavingcomment'] = 'Error saving the comment for question {$a->name} in the database.';
 $string['errorupdatingattempt'] = 'Error updating attempt {$a->id} in the database.';
+$string['eventquestioncategorycreated'] = 'Question category created';
 $string['exportcategory'] = 'Export category';
 $string['exportcategory_help'] = 'This setting determines the category from which the exported questions will be taken.
 
index 33870ce..19069d8 100644 (file)
@@ -338,7 +338,7 @@ abstract class base implements \IteratorAggregate {
         }
 
         if (!class_exists($classname)) {
-            return false;
+            return self::restore_unknown($data, $logextra);
         }
         $event = new $classname();
         if (!($event instanceof \core\event\base)) {
@@ -370,6 +370,27 @@ abstract class base implements \IteratorAggregate {
         return $event;
     }
 
+    /**
+     * Restore unknown event.
+     *
+     * @param array $data
+     * @param array $logextra
+     * @return unknown_logged
+     */
+    protected static final function restore_unknown(array $data, array $logextra) {
+        $classname = '\core\event\unknown_logged';
+
+        /** @var unknown_logged $event */
+        $event = new $classname();
+        $event->restored = true;
+        $event->triggered = true;
+        $event->dispatched = true;
+        $event->data = $data;
+        $event->logextra = $logextra;
+
+        return $event;
+    }
+
     /**
      * Create fake event from legacy log data.
      *
diff --git a/lib/classes/event/question_category_created.php b/lib/classes/event/question_category_created.php
new file mode 100644 (file)
index 0000000..a620065
--- /dev/null
@@ -0,0 +1,80 @@
+<?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/>.
+
+/**
+ * Question category created event class.
+ *
+ * @package    core
+ * @since      Moodle 2.7
+ * @copyright  2014 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace core\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+class question_category_created extends base {
+
+    /**
+     * Init method.
+     */
+    protected function init() {
+        $this->data['objecttable'] = 'question_categories';
+        $this->data['crud'] = 'c';
+        $this->data['edulevel'] = self::LEVEL_TEACHING;
+    }
+
+    /**
+     * Returns localised general event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('eventquestioncategorycreated', 'question');
+    }
+
+    /**
+     * Returns description of what happened.
+     *
+     * @return string
+     */
+    public function get_description() {
+        return 'A question category with the id of ' . $this->objectid . ' was created by a user with the id ' . $this->userid;
+    }
+
+    /**
+     * Returns relevant URL.
+     *
+     * @return \moodle_url
+     */
+    public function get_url() {
+        if ($this->contextlevel == CONTEXT_MODULE) {
+            return new \moodle_url('/question/category.php', array('cmid' => $this->contextinstanceid));
+        } else {
+            return new \moodle_url('/question/category.php', array('courseid' => $this->courseid));
+        }
+    }
+
+    /**
+     * Return the legacy event log data.
+     *
+     * @return array
+     */
+    protected function get_legacy_logdata() {
+        return array($this->courseid, 'quiz', 'addcategory', 'view.php?id=' . $this->contextinstanceid,
+            $this->objectid, $this->contextinstanceid);
+    }
+}
diff --git a/lib/classes/event/unknown_logged.php b/lib/classes/event/unknown_logged.php
new file mode 100644 (file)
index 0000000..8d9066d
--- /dev/null
@@ -0,0 +1,40 @@
+<?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;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Unknown event class.
+ *
+ * @package    core
+ * @copyright  2014 Petr Skoda
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class unknown_logged extends base {
+    public function init() {
+        throw new \coding_exception('unknown events cannot be triggered');
+    }
+
+    public static function get_name() {
+        return get_string('eventunknownlogged', 'core');
+    }
+
+    public function get_description() {
+        return 'Unknown event (' . $this->eventname . ')';
+    }
+}
index e6d13b3..06612bb 100644 (file)
@@ -107,14 +107,29 @@ function css_write_file($filename, $content) {
 /**
  * Takes CSS and chunks it if the number of selectors within it exceeds $maxselectors.
  *
+ * The chunking will not split a group of selectors, or a media query. That means that
+ * if n > $maxselectors and there are n selectors grouped together,
+ * they will not be chunked and you could end up with more selectors than desired.
+ * The same applies for a media query that has more than n selectors.
+ *
+ * Also, as we do not split group of selectors or media queries, the chunking might
+ * not be as optimal as it could be, having files with less selectors than it could
+ * potentially contain.
+ *
+ * String functions used here are not compliant with unicode characters. But that is
+ * not an issue as the syntax of CSS is using ASCII codes. Even if we have unicode
+ * characters in comments, or in the property 'content: ""', it will behave correcly.
+ *
+ * Please note that this strips out the comments if chunking happens.
+ *
  * @param string $css The CSS to chunk.
  * @param string $importurl The URL to use for import statements.
  * @param int $maxselectors The number of selectors to limit a chunk to.
- * @param int $buffer The buffer size to use when chunking. You shouldn't need to reduce this
- *      unless you are lowering the maximum selectors.
+ * @param int $buffer Not used any more.
  * @return array An array of CSS chunks.
  */
 function css_chunk_by_selector_count($css, $importurl, $maxselectors = 4095, $buffer = 50) {
+
     // Check if we need to chunk this CSS file.
     $count = substr_count($css, ',') + substr_count($css, '{');
     if ($count < $maxselectors) {
@@ -122,44 +137,116 @@ function css_chunk_by_selector_count($css, $importurl, $maxselectors = 4095, $bu
         return array($css);
     }
 
-    // Chunk time ?!
-    // Split the CSS by array, making sure to save the delimiter in the process.
-    $parts = preg_split('#([,\}])#', $css, null, PREG_SPLIT_DELIM_CAPTURE + PREG_SPLIT_NO_EMPTY);
-    // We need to chunk the array. Each delimiter is stored separately so we multiple by 2.
-    // We also subtract 100 to give us a small buffer just in case.
-    $parts = array_chunk($parts, $maxselectors * 2 - $buffer * 2);
-    $css = array();
-    $partcount = count($parts);
-    foreach ($parts as $key => $chunk) {
-        if (end($chunk) === ',') {
-            // Damn last element was a comma.
-            // Pretty much the only way to deal with this is to take the styles from the end of the
-            // comma separated chain of selectors and apply it to the last selector we have here in place
-            // of the comma.
-            // Unit tests are essential for making sure this works.
-            $styles = false;
-            $i = $key;
-            while ($styles === false && $i < ($partcount - 1)) {
-                $i++;
-                $nextpart = $parts[$i];
-                foreach ($nextpart as $style) {
-                    if (strpos($style, '{') !== false) {
-                        $styles = preg_replace('#^[^\{]+#', '', $style);
-                        break;
-                    }
+    $chunks = array();                  // The final chunks.
+    $offsets = array();                 // The indexes to chunk at.
+    $offset = 0;                        // The current offset.
+    $selectorcount = 0;                 // The number of selectors since the last split.
+    $lastvalidoffset = 0;               // The last valid index to split at.
+    $lastvalidoffsetselectorcount = 0;  // The number of selectors used at the time were could split.
+    $inrule = 0;                        // The number of rules we are in, should not be greater than 1.
+    $inmedia = false;                   // Whether or not we are in a media query.
+    $mediacoming = false;               // Whether or not we are expeting a media query.
+    $currentoffseterror = null;         // Not null when we have recorded an error for the current split.
+    $offseterrors = array();            // The offsets where we found errors.
+
+    // Remove the comments. Because it's easier, safer and probably a lot of other good reasons.
+    $css = preg_replace('#/\*(.*?)\*/#s', '', $css);
+    $strlen = strlen($css);
+
+    // Walk through the CSS content character by character.
+    for ($i = 1; $i <= $strlen; $i++) {
+        $char = $css[$i - 1];
+        $offset = $i;
+
+        // Is that a media query that I see coming towards us?
+        if ($char === '@') {
+            if (!$inmedia && substr($css, $offset, 5) === 'media') {
+                $mediacoming = true;
+            }
+        }
+
+        // So we are entering a rule or a media query...
+        if ($char === '{') {
+            if ($mediacoming) {
+                $inmedia = true;
+                $mediacoming = false;
+            } else {
+                $inrule++;
+                $selectorcount++;
+            }
+        }
+
+        // Let's count the number of selectors, but only if we are not in a rule as they
+        // can contain commas too.
+        if (!$inrule && $char === ',') {
+            $selectorcount++;
+        }
+
+        // We reached the end of something.
+        if ($char === '}') {
+            // Oh, we are in a media query.
+            if ($inmedia) {
+                if (!$inrule) {
+                    // This is the end of the media query.
+                    $inmedia = false;
+                } else {
+                    // We were in a rule, in the media query.
+                    $inrule--;
                 }
+            } else {
+                $inrule--;
+            }
+
+            // We are not in a media query, and there is no pending rule, it is safe to split here.
+            if (!$inmedia && !$inrule) {
+                $lastvalidoffset = $offset;
+                $lastvalidoffsetselectorcount = $selectorcount;
             }
-            if ($styles === false) {
-                $styles = '/** Error chunking CSS **/';
+        }
+
+        // Alright, this is splitting time...
+        if ($selectorcount > $maxselectors) {
+            if (!$lastvalidoffset) {
+                // We must have reached more selectors into one set than we were allowed. That means that either
+                // the chunk size value is too small, or that we have a gigantic group of selectors, or that a media
+                // query contains more selectors than the chunk size. We have to ignore this because we do not
+                // support split inside a group of selectors or media query.
+                if ($currentoffseterror === null) {
+                    $currentoffseterror = $offset;
+                    $offseterrors[] = $currentoffseterror;
+                }
             } else {
-                $styles .= '}';
+                // We identify the offset to split at and reset the number of selectors found from there.
+                $offsets[] = $lastvalidoffset;
+                $selectorcount = $selectorcount - $lastvalidoffsetselectorcount;
+                $lastvalidoffset = 0;
+                $currentoffseterror = null;
             }
-            array_pop($chunk);
-            array_push($chunk, $styles);
         }
-        $css[] = join('', $chunk);
     }
-    // The array $css now contains CSS split into perfect sized chunks.
+
+    // Report offset errors.
+    if (!empty($offseterrors)) {
+        debugging('Could not find a safe place to split at offset(s): ' . implode(', ', $offseterrors) . '. Those were ignored.',
+            DEBUG_DEVELOPER);
+    }
+
+    // Now that we have got the offets, we can chunk the CSS.
+    $offsetcount = count($offsets);
+    foreach ($offsets as $key => $index) {
+        $start = 0;
+        if ($key > 0) {
+            $start = $offsets[$key - 1];
+        }
+        // From somewhere up to the offset.
+        $chunks[] = substr($css, $start, $index - $start);
+    }
+    // Add the last chunk (if there is one), from the last offset to the end of the string.
+    if (end($offsets) != $strlen) {
+        $chunks[] = substr($css, end($offsets));
+    }
+
+    // The array $chunks now contains CSS split into perfect sized chunks.
     // Import statements can only appear at the very top of a CSS file.
     // Imported sheets are applied in the the order they are imported and
     // are followed by the contents of the CSS.
@@ -170,7 +257,7 @@ function css_chunk_by_selector_count($css, $importurl, $maxselectors = 4095, $bu
     // followed by the contents of the final chunk in the actual sheet.
     $importcss = '';
     $slashargs = strpos($importurl, '.php?') === false;
-    $parts = count($css);
+    $parts = count($chunks);
     for ($i = 1; $i < $parts; $i++) {
         if ($slashargs) {
             $importcss .= "@import url({$importurl}/chunk{$i});\n";
@@ -178,10 +265,10 @@ function css_chunk_by_selector_count($css, $importurl, $maxselectors = 4095, $bu
             $importcss .= "@import url({$importurl}&chunk={$i});\n";
         }
     }
-    $importcss .= end($css);
-    $css[key($css)] = $importcss;
+    $importcss .= end($chunks);
+    $chunks[key($chunks)] = $importcss;
 
-    return $css;
+    return $chunks;
 }
 
 /**
index d93c067..3b29fd0 100644 (file)
@@ -943,7 +943,9 @@ $services = array(
             'mod_assign_submit_for_grading',
             'mod_assign_save_grade',
             'mod_assign_save_user_extensions',
-            'mod_assign_reveal_identities'),
+            'mod_assign_reveal_identities',
+            'message_airnotifier_is_system_configured',
+            'message_airnotifier_are_notification_preferences_configured'),
         'enabled' => 0,
         'restrictedusers' => 0,
         'shortname' => MOODLE_OFFICIAL_MOBILE_SERVICE,
index 2077de3..9a488ac 100644 (file)
Binary files a/lib/editor/atto/plugins/table/yui/build/moodle-atto_table-button/moodle-atto_table-button-debug.js and b/lib/editor/atto/plugins/table/yui/build/moodle-atto_table-button/moodle-atto_table-button-debug.js differ
index 7220bc6..b32f788 100644 (file)
Binary files a/lib/editor/atto/plugins/table/yui/build/moodle-atto_table-button/moodle-atto_table-button-min.js and b/lib/editor/atto/plugins/table/yui/build/moodle-atto_table-button/moodle-atto_table-button-min.js differ
index 2077de3..9a488ac 100644 (file)
Binary files a/lib/editor/atto/plugins/table/yui/build/moodle-atto_table-button/moodle-atto_table-button.js and b/lib/editor/atto/plugins/table/yui/build/moodle-atto_table-button/moodle-atto_table-button.js differ
index 750822d..a7f81e4 100644 (file)
@@ -177,25 +177,28 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
      * @private
      */
     _displayTableEditor: function(e) {
-        var cell = this._getSuitableTableCell();
+        var selection = this.get('host').getSelectionParentNode(),
+            cell;
+
+        if (!selection) {
+            // We don't have a current selection at all, so show the standard dialogue.
+            return this._displayDialogue(e);
+        }
+
+        // Check all of the table cells found in the selection.
+        Y.one(selection).ancestors('th, td', true).each(function(node) {
+            if (this.editor.contains(node)) {
+                cell = node;
+            }
+        }, this);
+
         if (cell) {
             // Add the cell to the EventFacade to save duplication in when showing the menu.
             e.tableCell = cell;
             return this._showTableMenu(e);
         }
-        return this._displayDialogue(e);
-    },
 
-    /**
-     * Returns whether or not the parameter node exists within the editor.
-     *
-     * @method _stopAtContentEditableFilter
-     * @param  {Node} node
-     * @private
-     * @return {boolean} whether or not the parameter node exists within the editor.
-     */
-    _stopAtContentEditableFilter: function(node) {
-        this.editor.contains(node);
+        return this._displayDialogue(e);
     },
 
     /**
@@ -242,44 +245,6 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
         return this._content;
     },
 
-    /**
-     * Given the current selection, return a table cell suitable for table editing
-     * purposes, i.e. the first table cell selected, or the first cell in the table
-     * that the selection exists in, or null if not within a table.
-     *
-     * @method _getSuitableTableCell
-     * @private
-     * @return {Node} suitable target cell, or null if not within a table
-     */
-    _getSuitableTableCell: function() {
-        var targetcell = null,
-            host = this.get('host');
-
-        host.getSelectedNodes().some(function (node) {
-            if (node.ancestor('td, th, caption', true, this._stopAtContentEditableFilter)) {
-                targetcell = node;
-
-                var caption = node.ancestor('caption', true, this._stopAtContentEditableFilter);
-                if (caption) {
-                    var table = caption.get('parentNode');
-                    if (table) {
-                        targetcell = table.one('td, th');
-                    }
-                }
-
-                // Once we've found a cell to target, we shouldn't need to keep looking.
-                return true;
-            }
-        });
-
-        if (targetcell) {
-            var selection = host.getSelectionFromNode(targetcell);
-            host.setSelection(selection);
-        }
-
-        return targetcell;
-    },
-
     /**
      * Change a node from one type to another, copying all attributes and children.
      *
@@ -605,6 +570,11 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
                     data: {
                         change: "deletecolumn"
                     }
+                }, {
+                    text: M.util.get_string("edittable", COMPONENT),
+                    data: {
+                        change: "edittable"
+                    }
                 }
             ];
 
index c416a6f..84be800 100644 (file)
@@ -155,7 +155,7 @@ div.editor_atto_content:hover .atto_control {
     display: block;
 }
 
-.yui3-menu-hidden {
+.editor_atto_menu.yui3-menu-hidden {
     display: none;
 }
 
index 70434d2..75ede19 100644 (file)
Binary files a/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-debug.js and b/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-debug.js differ
index ed3bea3..2b4110d 100644 (file)
Binary files a/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-min.js and b/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-min.js differ
index 60740c0..50973bd 100644 (file)
Binary files a/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor.js and b/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor.js differ
index 5149d80..3c7dca6 100644 (file)
Binary files a/lib/editor/atto/yui/build/moodle-editor_atto-plugin/moodle-editor_atto-plugin-debug.js and b/lib/editor/atto/yui/build/moodle-editor_atto-plugin/moodle-editor_atto-plugin-debug.js differ
index f22d5ad..4e29469 100644 (file)
Binary files a/lib/editor/atto/yui/build/moodle-editor_atto-plugin/moodle-editor_atto-plugin-min.js and b/lib/editor/atto/yui/build/moodle-editor_atto-plugin/moodle-editor_atto-plugin-min.js differ
index e1cb432..4fd24e4 100644 (file)
Binary files a/lib/editor/atto/yui/build/moodle-editor_atto-plugin/moodle-editor_atto-plugin.js and b/lib/editor/atto/yui/build/moodle-editor_atto-plugin/moodle-editor_atto-plugin.js differ
index daa1661..33c2ba4 100644 (file)
@@ -600,6 +600,9 @@ EditorPluginButtons.prototype = {
             this.get('host').focus();
         }
 
+        // Save the selection.
+        this.get('host').saveSelection();
+
         // Ensure that we focus on this button next time.
         if (creatorButton) {
             this.get('host')._setTabFocus(creatorButton);
index 2771a8a..4f4e630 100644 (file)
@@ -88,6 +88,7 @@ EditorToolbarNav.prototype = {
         button = this._findFirstFocusable(buttons, current, direction);
         if (button) {
             button.focus();
+            this._setTabFocus(button);
         } else {
             Y.log("Unable to find a button to focus on", 'debug', LOGNAME);
         }
index 6b89395..053b28d 100644 (file)
@@ -2234,6 +2234,7 @@ class cached_cm_info {
  *    visible or not available, so this would be true in that case) - obtained dynamically
  * @property-read string $sequence Comma-separated list of all modules in the section. Note, this field may not exactly
  *    match course_sections.sequence if later has references to non-existing modules or not modules of not available module types.
+ * @property-read course_modinfo $modinfo
  */
 class section_info implements IteratorAggregate {
     /**
@@ -2649,6 +2650,15 @@ class section_info implements IteratorAggregate {
         return $this->modinfo->get_course_id();
     }
 
+    /**
+     * Modinfo object
+     *
+     * @return course_modinfo
+     */
+    private function get_modinfo() {
+        return $this->modinfo;
+    }
+
     /**
      * Prepares section data for inclusion in sectioncache cache, removing items
      * that are set to defaults, and adding availability data if required.
index ed350cd..4cb804e 100644 (file)
@@ -1261,7 +1261,7 @@ function disable_output_buffering() {
  */
 function redirect_if_major_upgrade_required() {
     global $CFG;
-    $lastmajordbchanges = 2014040408.00;
+    $lastmajordbchanges = 2014040800.00;
     if (empty($CFG->version) or (float)$CFG->version < $lastmajordbchanges or
             during_initial_install() or !empty($CFG->adminsetuppending)) {
         try {
index a798e20..8ba3a26 100644 (file)
@@ -1082,7 +1082,7 @@ CSS;
     public function test_css_chunking() {
         // Test with an even number of styles.
         $css = 'a{}b{}c{}d{}e{}f{}';
-        $chunks = css_chunk_by_selector_count($css, 'styles.php?type=test', 2, 0);
+        $chunks = css_chunk_by_selector_count($css, 'styles.php?type=test', 2);
         $this->assertInternalType('array', $chunks);
         $this->assertCount(3, $chunks);
         $this->assertArrayHasKey(0, $chunks);
@@ -1094,7 +1094,7 @@ CSS;
 
         // Test with an odd number of styles.
         $css = 'a{}b{}c{}d{}e{}';
-        $chunks = css_chunk_by_selector_count($css, 'styles.php?type=test', 2, 0);
+        $chunks = css_chunk_by_selector_count($css, 'styles.php?type=test', 2);
         $this->assertInternalType('array', $chunks);
         $this->assertCount(3, $chunks);
         $this->assertArrayHasKey(0, $chunks);
@@ -1104,21 +1104,9 @@ CSS;
         $this->assertSame('c{}d{}', $chunks[1]);
         $this->assertSame("@import url(styles.php?type=test&chunk=1);\n@import url(styles.php?type=test&chunk=2);\ne{}", $chunks[2]);
 
-        // Test buffering. Set a buffer that will reduce the effective sheet size back to two.
-        $css = 'a{}b{}c{}d{}e{}f{}';
-        $chunks = css_chunk_by_selector_count($css, 'styles.php?type=test', 6, 4);
-        $this->assertInternalType('array', $chunks);
-        $this->assertCount(3, $chunks);
-        $this->assertArrayHasKey(0, $chunks);
-        $this->assertArrayHasKey(1, $chunks);
-        $this->assertArrayHasKey(2, $chunks);
-        $this->assertSame('a{}b{}', $chunks[0]);
-        $this->assertSame('c{}d{}', $chunks[1]);
-        $this->assertSame("@import url(styles.php?type=test&chunk=1);\n@import url(styles.php?type=test&chunk=2);\ne{}f{}", $chunks[2]);
-
         // Test well placed commas.
         $css = 'a,b{}c,d{}e,f{}';
-        $chunks = css_chunk_by_selector_count($css, 'styles.php?type=test', 2, 0);
+        $chunks = css_chunk_by_selector_count($css, 'styles.php?type=test', 2);
         $this->assertInternalType('array', $chunks);
         $this->assertCount(3, $chunks);
         $this->assertArrayHasKey(0, $chunks);
@@ -1130,56 +1118,117 @@ CSS;
 
         // Test unfortunately placed commas.
         $css = 'a{}b,c{color:red;}d{}e{}f{}';
-        $chunks = css_chunk_by_selector_count($css, 'styles.php?type=test', 2, 0);
+        $chunks = css_chunk_by_selector_count($css, 'styles.php?type=test', 2);
         $this->assertInternalType('array', $chunks);
-        $this->assertCount(3, $chunks);
+        $this->assertCount(4, $chunks);
         $this->assertArrayHasKey(0, $chunks);
         $this->assertArrayHasKey(1, $chunks);
         $this->assertArrayHasKey(2, $chunks);
-        $this->assertSame('a{}b{color:red;}', $chunks[0]);
-        $this->assertSame('c{color:red;}d{}', $chunks[1]);
-        $this->assertSame("@import url(styles.php?type=test&chunk=1);\n@import url(styles.php?type=test&chunk=2);\ne{}f{}", $chunks[2]);
+        $this->assertArrayHasKey(3, $chunks);
+        $this->assertSame('a{}', $chunks[0]);
+        $this->assertSame('b,c{color:red;}', $chunks[1]);
+        $this->assertSame('d{}e{}', $chunks[2]);
+        $this->assertSame("@import url(styles.php?type=test&chunk=1);\n@import url(styles.php?type=test&chunk=2);\n@import url(styles.php?type=test&chunk=3);\nf{}", $chunks[3]);
 
         // Test unfortunate CSS.
         $css = 'a,b,c,d,e,f{color:red;}';
         $chunks = css_chunk_by_selector_count($css, 'styles.php?type=test', 2, 0);
         $this->assertInternalType('array', $chunks);
-        $this->assertCount(3, $chunks);
+        $this->assertCount(1, $chunks);
         $this->assertArrayHasKey(0, $chunks);
-        $this->assertArrayHasKey(1, $chunks);
-        $this->assertArrayHasKey(2, $chunks);
-        $this->assertSame('a,b{color:red;}', $chunks[0]);
-        $this->assertSame('c,d{color:red;}', $chunks[1]);
-        $this->assertSame("@import url(styles.php?type=test&chunk=1);\n@import url(styles.php?type=test&chunk=2);\ne,f{color:red;}", $chunks[2]);
+        $this->assertSame('a,b,c,d,e,f{color:red;}', $chunks[0]);
+        $this->assertDebuggingCalled('Could not find a safe place to split at offset(s): 6. Those were ignored.');
 
         // Test to make sure invalid CSS isn't totally ruined.
         $css = 'a{},,,e{},';
-        $chunks = css_chunk_by_selector_count($css, 'styles.php?type=test', 2, 0);
+        $chunks = css_chunk_by_selector_count($css, 'styles.php?type=test', 2);
         // Believe it or not we want to care what comes out here as this will be parsed correctly
         // by a browser.
         $this->assertInternalType('array', $chunks);
-        $this->assertCount(2, $chunks);
+        $this->assertCount(3, $chunks);
         $this->assertArrayHasKey(0, $chunks);
         $this->assertArrayHasKey(1, $chunks);
-        $this->assertSame('a{},{}', $chunks[0]);
-        $this->assertSame("@import url(styles.php?type=test&chunk=1);\n,e{}/** Error chunking CSS **/", $chunks[1]);
+        $this->assertArrayHasKey(2, $chunks);
+        $this->assertSame('a{}', $chunks[0]);
+        $this->assertSame(',,,e{}', $chunks[1]);
+        $this->assertSame("@import url(styles.php?type=test&chunk=1);\n@import url(styles.php?type=test&chunk=2);\n,", $chunks[2]);
+        $this->assertDebuggingCalled('Could not find a safe place to split at offset(s): 6. Those were ignored.');
 
         // Test utter crap CSS to make sure we don't loop to our deaths.
         $css = 'a,b,c,d,e,f';
-        $chunks = css_chunk_by_selector_count($css, 'styles.php?type=test', 2, 0);
+        $chunks = css_chunk_by_selector_count($css, 'styles.php?type=test', 2);
         $this->assertInternalType('array', $chunks);
-        $this->assertCount(3, $chunks);
+        $this->assertCount(1, $chunks);
         $this->assertArrayHasKey(0, $chunks);
-        $this->assertArrayHasKey(1, $chunks);
-        $this->assertArrayHasKey(2, $chunks);
-        $this->assertSame('a,b/** Error chunking CSS **/', $chunks[0]);
-        $this->assertSame('c,d/** Error chunking CSS **/', $chunks[1]);
-        $this->assertSame("@import url(styles.php?type=test&chunk=1);\n@import url(styles.php?type=test&chunk=2);\ne,f", $chunks[2]);
+        $this->assertSame($css, $chunks[0]);
+        $this->assertDebuggingCalled('Could not find a safe place to split at offset(s): 6. Those were ignored.');
+
         // Test another death situation to make sure we're invincible.
         $css = 'a,,,,,e';
-        $chunks = css_chunk_by_selector_count($css, 'styles.php?type=test', 2, 0);
+        $chunks = css_chunk_by_selector_count($css, 'styles.php?type=test', 2);
         $this->assertInternalType('array', $chunks);
+        $this->assertDebuggingCalled('Could not find a safe place to split at offset(s): 4. Those were ignored.');
         // I don't care what the outcome is, I just want to make sure it doesn't die.
+
+        // Test media queries.
+        $css = '@media (min-width: 980px) { .a,.b{} }';
+        $chunks = css_chunk_by_selector_count($css, 'styles.php?type=test', 2);
+        $this->assertCount(1, $chunks);
+        $this->assertSame('@media (min-width: 980px) { .a,.b{} }', $chunks[0]);
+
+        // Test special rules.
+        $css = 'a,b{ background-image: linear-gradient(to bottom, #ffffff, #cccccc);}d,e{}';
+        $chunks = css_chunk_by_selector_count($css, 'styles.php?type=test', 2);
+        $this->assertCount(2, $chunks);
+        $this->assertSame('a,b{ background-image: linear-gradient(to bottom, #ffffff, #cccccc);}', $chunks[0]);
+        $this->assertSame("@import url(styles.php?type=test&chunk=1);\nd,e{}", $chunks[1]);
+
+        // Test media queries with too many selectors.
+        $css = '@media (min-width: 980px) { a,b,c,d{} }';
+        $chunks = css_chunk_by_selector_count($css, 'styles.php?type=test', 2);
+        $this->assertCount(1, $chunks);
+        $this->assertSame('@media (min-width: 980px) { a,b,c,d{} }', $chunks[0]);
+        $this->assertDebuggingCalled('Could not find a safe place to split at offset(s): 34. Those were ignored.');
+
+        // Complex test.
+        $css = '@media (a) {b{}} c{} d,e{} f,g,h{} i,j{x:a,b,c} k,l{} @media(x){l,m{ y: a,b,c}} n{}';
+        $chunks = css_chunk_by_selector_count($css, 'styles.php?type=test', 3);
+        $this->assertCount(6, $chunks);
+        $this->assertSame('@media (a) {b{}} c{}', $chunks[0]);
+        $this->assertSame(' d,e{}', $chunks[1]);
+        $this->assertSame(' f,g,h{}', $chunks[2]);
+        $this->assertSame(' i,j{x:a,b,c}', $chunks[3]);
+        $this->assertSame(' k,l{}', $chunks[4]);
+        $this->assertSame("@import url(styles.php?type=test&chunk=1);\n@import url(styles.php?type=test&chunk=2);\n@import url(styles.php?type=test&chunk=3);\n@import url(styles.php?type=test&chunk=4);\n@import url(styles.php?type=test&chunk=5);\n @media(x){l,m{ y: a,b,c}} n{}", $chunks[5]);
+
+        // Multiple offset errors.
+        $css = 'a,b,c{} d,e,f{}';
+        $chunks = css_chunk_by_selector_count($css, 'styles.php?type=test', 2);
+        $this->assertCount(2, $chunks);
+        $this->assertSame('a,b,c{}', $chunks[0]);
+        $this->assertSame("@import url(styles.php?type=test&chunk=1);\n d,e,f{}", $chunks[1]);
+        $this->assertDebuggingCalled('Could not find a safe place to split at offset(s): 6, 14. Those were ignored.');
+
+        // Test the split according to IE.
+        $css = str_repeat('a{}', 4100);
+        $chunks = css_chunk_by_selector_count($css, 'styles.php?type=test');
+        $this->assertCount(2, $chunks);
+        $this->assertSame(str_repeat('a{}', 4095), $chunks[0]);
+        $this->assertSame("@import url(styles.php?type=test&chunk=1);\n" . str_repeat('a{}', 5), $chunks[1]);
+
+        // Test strip out comments.
+        $css = ".a {/** a\nb\nc */} /** a\nb\nc */ .b{} /** .c,.d{} */ e{}";
+        $chunks = css_chunk_by_selector_count($css, 'styles.php?type=test', 2);
+        $this->assertCount(2, $chunks);
+        $this->assertSame('.a {}  .b{}', $chunks[0]);
+        $this->assertSame("@import url(styles.php?type=test&chunk=1);\n  e{}", $chunks[1]);
+
+        // Test something with unicode characters.
+        $css = 'a,b{} nav a:hover:after { content: "↓"; } b{ color:test;}';
+        $chunks = css_chunk_by_selector_count($css, 'styles.php?type=test', 2);
+        $this->assertCount(2, $chunks);
+        $this->assertSame('a,b{}', $chunks[0]);
+        $this->assertSame("@import url(styles.php?type=test&chunk=1);\n nav a:hover:after { content: \"↓\"; } b{ color:test;}", $chunks[1]);
     }
 
     /**
diff --git a/lib/tests/event_unknown_logged_test.php b/lib/tests/event_unknown_logged_test.php
new file mode 100644 (file)
index 0000000..7d07dce
--- /dev/null
@@ -0,0 +1,53 @@
+<?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/>.
+
+/**
+ * Tests for event manager, base event and observers.
+ *
+ * @package    core
+ * @category   phpunit
+ * @copyright  2014 Petr Skoda
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once(__DIR__.'/fixtures/event_fixtures.php');
+
+class core_event_unknown_logged_testcase extends advanced_testcase {
+
+    public function test_restore_event() {
+        $event1 = \core_tests\event\unittest_executed::create(array('context' => context_system::instance(), 'other' => array('sample' => 1, 'xx' => 10)));
+        $data1 = $event1->get_data();
+
+        $data1['eventname'] = '\mod_xx\event\xx_yy';
+        $data1['component'] = 'mod_xx';
+        $data1['action'] = 'yy';
+        $data1['target'] = 'xx';
+        $extra1 = array('origin' => 'cli');
+
+        $event2 = \core\event\base::restore($data1, $extra1);
+        $data2 = $event2->get_data();
+        $extra2 = $event2->get_logextra();
+
+        $this->assertInstanceOf('core\event\unknown_logged', $event2);
+        $this->assertTrue($event2->is_triggered());
+        $this->assertTrue($event2->is_restored());
+        $this->assertNull($event2->get_url());
+        $this->assertEquals($data1, $data2);
+        $this->assertEquals($extra1, $extra2);
+    }
+}
\ No newline at end of file
old mode 100755 (executable)
new mode 100644 (file)
index e08b5cf..f395c28
@@ -43,10 +43,6 @@ class message_airnotifier_manager {
     public function include_device_ajax() {
         global $PAGE, $CFG;
 
-        if (!$CFG->enableajax) {
-            return false;
-        }
-
         $config = new stdClass();
         $config->resturl = '/message/output/airnotifier/rest.php';
         $config->pageparams = array();
old mode 100755 (executable)
new mode 100644 (file)
old mode 100755 (executable)
new mode 100644 (file)
old mode 100755 (executable)
new mode 100644 (file)
old mode 100755 (executable)
new mode 100644 (file)
old mode 100755 (executable)
new mode 100644 (file)
index dbe89d5..b640a03
@@ -150,24 +150,26 @@ class message_airnotifier_external extends external_api {
                     }
 
                     foreach (array('loggedin', 'loggedoff') as $state) {
-                        if ($configured) {
-                            break;
-                        }
 
+                        $prefstocheck = array();
                         $prefname = 'message_provider_'.$provider->component.'_'.$provider->name.'_'.$state;
-                        $linepref = get_user_preferences($prefname, '', $user->id);
-                        if ($linepref == '') {
-                            continue;
+
+                        // First get forced settings.
+                        if ($forcedpref = get_config('message', $prefname)) {
+                            $prefstocheck = array_merge($prefstocheck, explode(',', $forcedpref));
+                        }
+
+                        // Then get user settings.
+                        if ($userpref = get_user_preferences($prefname, '', $user->id)) {
+                            $prefstocheck = array_merge($prefstocheck, explode(',', $userpref));
                         }
-                        $lineprefarray = explode(',', $linepref);
-
-                        foreach ($lineprefarray as $pref) {
-                            if ($pref == 'airnotifier') {
-                                $preferences['configured'] = 1;
-                                $configured = true;
-                                break;
-                            }
+
+                        if (in_array('airnotifier', $prefstocheck)) {
+                            $preferences['configured'] = 1;
+                            $configured = true;
+                            break;
                         }
+
                     }
                 }
 
@@ -210,4 +212,4 @@ class message_airnotifier_external extends external_api {
         );
     }
 
-}
\ No newline at end of file
+}
old mode 100755 (executable)
new mode 100644 (file)
index b1fe4cf..7a98b89
@@ -68,7 +68,8 @@ class message_output_airnotifier extends message_output {
             "type" => $eventdata->component . '_' . $eventdata->name,
             "device" => "xxxxxxxxxx",   // Since at this point we don't know the device, we use a 10 chars device platform.
             "notif" => "x",             // 1 or 0 wheter is a notification or not (it may be a private message).
-            "userfrom" => fullname($eventdata->userfrom));
+            "userfrom" => (!empty($eventdata->userfrom)) ? fullname($eventdata->userfrom) : ''
+        );
 
         // Calculate the size of the message knowing Apple payload must be lower than 256 bytes.
         // Airnotifier using few bytes of the payload, we must limit our message to even less characters.
@@ -109,10 +110,10 @@ class message_output_airnotifier extends message_output {
             $curl->setHeader($header);
             $params = array(
                 'alert'     => $message,
-                'date'      => $eventdata->timecreated,
+                'date'      => (!empty($eventdata->timecreated)) ? $eventdata->timecreated : time(),
                 'site'      => $siteid,
                 'type'      => $eventdata->component . '_' . $eventdata->name,
-                'userfrom'  => fullname($eventdata->userfrom),
+                'userfrom'  => (!empty($eventdata->userfrom)) ? fullname($eventdata->userfrom) : '',
                 'device'    => $devicetoken->platform,
                 'notif'     => (!empty($eventdata->notification)) ? '1' : '0',
                 'token'     => $devicetoken->pushid);
old mode 100755 (executable)
new mode 100644 (file)
index 457832d..0b5ff28
@@ -25,7 +25,7 @@
 require('../../../config.php');
 require_once($CFG->dirroot . '/' . $CFG->admin . '/registration/lib.php');
 
-define('AIRNOTIFIER_PUBLICURL', 'http://messages.moodle.net');
+define('AIRNOTIFIER_PUBLICURL', 'https://messages.moodle.net');
 
 $PAGE->set_url(new moodle_url('/message/output/airnotifier/requestaccesskey.php'));
 $PAGE->set_context(context_system::instance());
old mode 100755 (executable)
new mode 100644 (file)
old mode 100755 (executable)
new mode 100644 (file)
index d788f75..5eaed4a
@@ -27,7 +27,7 @@ if ($ADMIN->fulltree) {
     // The processor should be enabled by the same enable mobile setting.
     $settings->add(new admin_setting_configtext('airnotifierurl',
                     get_string('airnotifierurl', 'message_airnotifier'),
-                    get_string('configairnotifierurl', 'message_airnotifier'), 'http://messages.moodle.net', PARAM_URL));
+                    get_string('configairnotifierurl', 'message_airnotifier'), 'https://messages.moodle.net', PARAM_URL));
     $settings->add(new admin_setting_configtext('airnotifierport',
                     get_string('airnotifierport', 'message_airnotifier'),
                     get_string('configairnotifierport', 'message_airnotifier'), '80', PARAM_INT));
old mode 100755 (executable)
new mode 100644 (file)
old mode 100755 (executable)
new mode 100644 (file)
index f1028d4..c2a0ea2 100644 (file)
@@ -49,6 +49,11 @@ if (($newstate != $entry->approved) && confirm_sesskey()) {
     if ($completion->is_enabled($cm) == COMPLETION_TRACKING_AUTOMATIC && $glossary->completionentries) {
         $completion->update_state($cm, COMPLETION_COMPLETE, $entry->userid);
     }
+
+    // Reset caches.
+    if ($entry->usedynalink) {
+        \mod_glossary\local\concept_cache::reset_glossary($glossary);
+    }
 }
 
 redirect("view.php?id=$cm->id&amp;mode=$mode&amp;hook=$hook");
diff --git a/mod/glossary/classes/local/concept_cache.php b/mod/glossary/classes/local/concept_cache.php
new file mode 100644 (file)
index 0000000..a7f42b9
--- /dev/null
@@ -0,0 +1,289 @@
+<?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/>.
+
+/**
+ * Entry caching for glossary filter.
+ *
+ * @package    mod_glossary
+ * @copyright  2014 Petr Skoda
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace mod_glossary\local;
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Concept caching for glossary filter.
+ *
+ * @package    mod_glossary
+ * @copyright  2014 Petr Skoda
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class concept_cache {
+    /**
+     * Event observer, do not call directly.
+     * @param \core\event\course_module_updated $event
+     */
+    public static function cm_updated(\core\event\course_module_updated $event) {
+        if ($event->other['modulename'] !== 'glossary') {
+            return;
+        }
+        // We do not know what changed exactly, so let's reset everything that might be affected.
+        concept_cache::reset_course_muc($event->courseid);
+        concept_cache::reset_global_muc();
+    }
+
+    /**
+     * Reset concept related caches.
+     * @param bool $phpunitreset
+     */
+    public static function reset_caches($phpunitreset = false) {
+        if ($phpunitreset) {
+            return;
+        }
+        $cache = \cache::make('mod_glossary', 'concepts');
+        $cache->purge();
+    }
+
+    /**
+     * Reset the cache for course concepts.
+     * @param int $courseid
+     */
+    public static function reset_course_muc($courseid) {
+        if (empty($courseid)) {
+            return;
+        }
+        $cache = \cache::make('mod_glossary', 'concepts');
+        $cache->delete((int)$courseid);
+    }
+
+    /**
+     * Reset the cache for global concepts.
+     */
+    public static function reset_global_muc() {
+        $cache = \cache::make('mod_glossary', 'concepts');
+        $cache->delete(0);
+    }
+
+    /**
+     * Utility method to purge caches related to given glossary.
+     * @param \stdClass $glossary
+     */
+    public static function reset_glossary($glossary) {
+        if (!$glossary->usedynalink) {
+            return;
+        }
+        self::reset_course_muc($glossary->course);
+        if ($glossary->globalglossary) {
+            self::reset_global_muc();
+        }
+    }
+
+    /**
+     * Fetch concepts for given glossaries.
+     * @param int[] $glossaries
+     * @return array
+     */
+    protected static function fetch_concepts(array $glossaries) {
+        global $DB;
+
+        $glossarylist = implode(',', $glossaries);
+
+        $sql = "SELECT id, glossaryid, concept, casesensitive, 0 AS category, fullmatch
+                  FROM {glossary_entries}
+                 WHERE glossaryid IN ($glossarylist) AND usedynalink = 1 AND approved = 1
+
+                 UNION
+
+                SELECT id, glossaryid, name AS concept, 1 AS casesensitive, 1 AS category, 1 AS fullmatch
+                  FROM {glossary_categories}
+                 WHERE glossaryid IN ($glossarylist) AND usedynalink = 1
+
+                UNION
+
+                SELECT ge.id, ge.glossaryid, ga.alias AS concept, ge.casesensitive, 0 AS category, ge.fullmatch
+                  FROM {glossary_alias} ga
+                  JOIN {glossary_entries} ge ON (ga.entryid = ge.id)
+                 WHERE ge.glossaryid IN ($glossarylist) AND ge.usedynalink = 1 AND ge.approved = 1";
+
+        $concepts = array();
+        $rs = $DB->get_recordset_sql($sql);
+        foreach ($rs as $concept) {
+            $currentconcept = trim(strip_tags($concept->concept));
+
+            // Concept must be HTML-escaped, so do the same as format_string to turn ampersands into &amp;.
+            $currentconcept = replace_ampersands_not_followed_by_entity($currentconcept);
+
+            if (empty($currentconcept)) {
+                continue;
+            }
+
+            // Rule out any small integers, see MDL-1446.
+            if (is_number($currentconcept) and $currentconcept < 1000) {
+                continue;
+            }
+
+            $concept->concept = $currentconcept;
+
+            $concepts[$concept->glossaryid][] = $concept;
+        }
+        $rs->close();
+
+        return $concepts;
+    }
+
+    /**
+     * Get all linked concepts from course.
+     * @param int $courseid
+     * @return array
+     */
+    protected static function get_course_concepts($courseid) {
+        global $DB;
+
+        if (empty($courseid)) {
+            return array(array(), array());
+        }
+
+        $courseid = (int)$courseid;
+
+        $cache = \cache::make('mod_glossary', 'concepts');
+        $data = $cache->get($courseid);
+        if (is_array($data)) {
+            list($glossaries, $allconcepts) = $data;
+
+        } else {
+            // Find all course glossaries.
+            $sql = "SELECT g.id, g.name
+                      FROM {glossary} g
+                      JOIN {course_modules} cm ON (cm.instance = g.id)
+                      JOIN {modules} m ON (m.name = 'glossary' AND m.id = cm.module)
+                     WHERE g.usedynalink = 1 AND g.course = :course AND cm.visible = 1 AND m.visible = 1
+                  ORDER BY g.globalglossary, g.id";
+            $glossaries = $DB->get_records_sql_menu($sql, array('course' => $courseid));
+            if (!$glossaries) {
+                $data = array(array(), array());
+                $cache->set($courseid, $data);
+                return $data;
+            }
+            foreach ($glossaries as $id => $name) {
+                $name = str_replace(':', '-', $name);
+                $glossaries[$id] = replace_ampersands_not_followed_by_entity(strip_tags($name));
+            }
+
+            $allconcepts = self::fetch_concepts(array_keys($glossaries));
+            foreach ($glossaries as $gid => $unused) {
+                if (!isset($allconcepts[$gid])) {
+                    unset($glossaries[$gid]);
+                }
+            }
+            if (!$glossaries) {
+                // This means there are no interesting concepts in the existing glossaries.
+                $data = array(array(), array());
+                $cache->set($courseid, $data);
+                return $data;
+            }
+            $cache->set($courseid, array($glossaries, $allconcepts));
+        }
+
+        $concepts = $allconcepts;
+
+        // Verify access control to glossary instances.
+        $modinfo = get_fast_modinfo($courseid);
+        $cminfos = $modinfo->get_instances_of('glossary');
+        foreach ($concepts as $modid => $unused) {
+            if (!isset($cminfos[$modid])) {
+                // This should not happen.
+                unset($concepts[$modid]);
+                unset($glossaries[$modid]);
+                continue;
+            }
+            if (!$cminfos[$modid]->uservisible) {
+                unset($concepts[$modid]);
+                unset($glossaries[$modid]);
+                continue;
+            }
+        }
+
+        return array($glossaries, $concepts);
+    }
+
+    /**
+     * Get all linked global concepts.
+     * @return array
+     */
+    protected static function get_global_concepts() {
+        global $DB;
+
+        $cache = \cache::make('mod_glossary', 'concepts');
+        $data = $cache->get(0);
+        if (is_array($data)) {
+            list($glossaries, $allconcepts) = $data;
+
+        } else {
+            // Find all global glossaries - no access control here.
+            $sql = "SELECT g.id, g.name
+                      FROM {glossary} g
+                      JOIN {course_modules} cm ON (cm.instance = g.id)
+                      JOIN {modules} m ON (m.name = 'glossary' AND m.id = cm.module)
+                     WHERE g.usedynalink = 1 AND g.globalglossary = 1 AND cm.visible = 1 AND m.visible = 1
+                  ORDER BY g.globalglossary, g.id";
+            $glossaries = $DB->get_records_sql_menu($sql);
+            if (!$glossaries) {
+                $data = array(array(), array());
+                $cache->set(0, $data);
+                return $data;
+            }
+            foreach ($glossaries as $id => $name) {
+                $name = str_replace(':', '-', $name);
+                $glossaries[$id] = replace_ampersands_not_followed_by_entity(strip_tags($name));
+            }
+            $allconcepts = self::fetch_concepts(array_keys($glossaries));
+            foreach ($glossaries as $gid => $unused) {
+                if (!isset($allconcepts[$gid])) {
+                    unset($glossaries[$gid]);
+                }
+            }
+            $cache->set(0, array($glossaries, $allconcepts));
+        }
+
+        // NOTE: no access control is here because it would be way too expensive to check access
+        //       to all courses that contain the global glossaries.
+        return array($glossaries, $allconcepts);
+    }
+
+    /**
+     * Get all concepts that should be linked in the given course.
+     * @param int $courseid
+     * @return array with two elements - array of glossaries and concepts for each glossary
+     */
+    public static function get_concepts($courseid) {
+        list($glossaries, $concepts) = self::get_course_concepts($courseid);
+        list($globalglossaries, $globalconcepts) = self::get_global_concepts();
+
+        foreach ($globalconcepts as $gid => $cs) {
+            if (!isset($concepts[$gid])) {
+                $concepts[$gid] = $cs;
+            }
+        }
+        foreach ($globalglossaries as $gid => $name) {
+            if (!isset($glossaries[$gid])) {
+                $glossaries[$gid] = $name;
+            }
+        }
+
+        return array($glossaries, $concepts);
+    }
+}
diff --git a/mod/glossary/db/caches.php b/mod/glossary/db/caches.php
new file mode 100644 (file)
index 0000000..d8584e1
--- /dev/null
@@ -0,0 +1,35 @@
+<?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/>.
+
+/**
+ * Glossary cache definitions.
+ *
+ * @package    mod_glossary
+ * @category   cache
+ * @copyright  2014 Petr Skoda
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+$definitions = array(
+    // This MUST NOT be a local cache, sorry cluster lovers.
+    'concepts' => array(
+        'mode' => cache_store::MODE_APPLICATION,
+        'simplekeys' => true, // The course id or 0 for global.
+        'simpledata' => false,
+        'staticacceleration' => true,
+        'staticaccelerationsize' => 30,
+    ),
+);
diff --git a/mod/glossary/db/events.php b/mod/glossary/db/events.php
new file mode 100644 (file)
index 0000000..fd8fef6
--- /dev/null
@@ -0,0 +1,32 @@
+<?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/>.
+
+/**
+ * Glossary event observer.
+ *
+ * @package    mod_glossary
+ * @copyright  2014 Petr Skoda
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$observers = array(
+    array (
+        'eventname' => '\core\event\course_module_updated',
+        'callback'  => '\mod_glossary\local\concept_cache::cm_updated',
+    ),
+);
\ No newline at end of file
index a45a698..7f5b64d 100644 (file)
@@ -125,6 +125,12 @@ if ($confirm and confirm_sesskey()) { // the operation was confirmed.
     ));
     $event->add_record_snapshot('glossary_entries', $origentry);
     $event->trigger();
+
+    // Reset caches.
+    if ($entry->usedynalink and $entry->approved) {
+        \mod_glossary\local\concept_cache::reset_glossary($glossary);
+    }
+
     redirect("view.php?id=$cm->id&amp;mode=$prevmode&amp;hook=$hook");
 
 } else {        // the operation has not been confirmed yet so ask the user to do so
index 4cec110..3ccb42c 100644 (file)
@@ -186,6 +186,17 @@ if ($mform->is_cancelled()){
         }
     }
 
+    // Reset caches.
+    if ($isnewentry) {
+        if ($entry->usedynalink and $entry->approved) {
+            \mod_glossary\local\concept_cache::reset_glossary($glossary);
+        }
+    } else {
+        // So many things may affect the linking, let's just purge the cache always on edit.
+        \mod_glossary\local\concept_cache::reset_glossary($glossary);
+    }
+
+
     redirect("view.php?id=$cm->id&mode=entry&hook=$entry->id");
 }
 
index 6e23158..c324447 100644 (file)
@@ -111,6 +111,9 @@ if ( $hook >0 ) {
             $event->add_record_snapshot('glossary', $glossary);
             $event->trigger();
 
+            // Reset caches.
+            \mod_glossary\local\concept_cache::reset_glossary($glossary);
+
         } else {
             echo $OUTPUT->header();
             echo $OUTPUT->heading(format_string($glossary->name), 2);
@@ -136,6 +139,9 @@ if ( $hook >0 ) {
             $event->add_record_snapshot('glossary', $glossary);
             $event->trigger();
 
+            // Reset caches.
+            \mod_glossary\local\concept_cache::reset_glossary($glossary);
+
             redirect("editcategories.php?id=$cm->id", get_string("categorydeleted", "glossary"), 2);
         } else {
             echo $OUTPUT->header();
@@ -201,6 +207,9 @@ if ( $hook >0 ) {
             $event->add_record_snapshot('glossary_categories', $cat);
             $event->add_record_snapshot('glossary', $glossary);
             $event->trigger();
+
+            // Reset caches.
+            \mod_glossary\local\concept_cache::reset_glossary($glossary);
         }
     } else {
         echo $OUTPUT->header();
index 39c7129..cb1d131 100644 (file)
@@ -272,6 +272,10 @@ if ($xml = glossary_read_imported_file($result)) {
             }
         }
     }
+
+    // Reset caches.
+    \mod_glossary\local\concept_cache::reset_glossary($glossary);
+
     // processed entries
     echo $OUTPUT->box_start('glossarydisplay generalbox');
     echo '<table class="glossaryimportexport">';
index 4440328..88ff8d9 100644 (file)
@@ -55,6 +55,7 @@ $string['attachment_help'] = 'You can optionally attach one or more files to a g
 $string['author'] = 'author';
 $string['authorview'] = 'Browse by Author';
 $string['back'] = 'Back';
+$string['cachedef_concepts'] = 'Concept linking';
 $string['cantinsertcat'] = 'Can\'t insert category';
 $string['cantinsertrec'] = 'Can\'t insert record';
 $string['cantinsertrel'] = 'Can\'t insert relation category-entry';
index 6f48e37..c956d7a 100644 (file)
@@ -201,7 +201,12 @@ function glossary_delete_instance($id) {
 
     glossary_grade_item_delete($glossary);
 
-    return $DB->delete_records('glossary', array('id'=>$id));
+    $DB->delete_records('glossary', array('id'=>$id));
+
+    // Reset caches.
+    \mod_glossary\local\concept_cache::reset_glossary($glossary);
+
+    return true;
 }
 
 /**
@@ -1145,19 +1150,12 @@ function  glossary_print_entry_concept($entry, $return=false) {
  * @param object $cm
  */
 function glossary_print_entry_definition($entry, $glossary, $cm) {
-    global $DB, $GLOSSARY_EXCLUDECONCEPTS;
+    global $GLOSSARY_EXCLUDEENTRY;
 
     $definition = $entry->definition;
 
-    //Calculate all the strings to be no-linked
-    //First, the concept
-    $GLOSSARY_EXCLUDECONCEPTS = array($entry->concept);
-    //Now the aliases
-    if ( $aliases = $DB->get_records('glossary_alias', array('entryid'=>$entry->id))) {
-        foreach ($aliases as $alias) {
-            $GLOSSARY_EXCLUDECONCEPTS[]=trim($alias->alias);
-        }
-    }
+    // Do not link self.
+    $GLOSSARY_EXCLUDEENTRY = $entry->id;
 
     $context = context_module::instance($cm->id);
     $definition = file_rewrite_pluginfile_urls($definition, 'pluginfile.php', $context->id, 'mod_glossary', 'entry', $entry->id);
@@ -1171,7 +1169,7 @@ function glossary_print_entry_definition($entry, $glossary, $cm) {
     $text = format_text($definition, $entry->definitionformat, $options);
 
     // Stop excluding concepts from autolinking
-    unset($GLOSSARY_EXCLUDECONCEPTS);
+    unset($GLOSSARY_EXCLUDEENTRY);
 
     if (!empty($entry->highlight)) {
         $text = highlight($entry->highlight, $text);
diff --git a/mod/glossary/tests/concept_cache_test.php b/mod/glossary/tests/concept_cache_test.php
new file mode 100644 (file)
index 0000000..5539e00
--- /dev/null
@@ -0,0 +1,181 @@
+<?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/>.
+
+/**
+ * Concept fetching and caching tests.
+ *
+ * @package    mod_glossary
+ * @category   test
+ * @copyright  2014 Petr Skoda
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+/**
+ * Concept fetching and caching tests.
+ *
+ * @package    mod_glossary
+ * @category   test
+ * @copyright  2014 Petr Skoda
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class mod_glossary_concept_cache_testcase extends advanced_testcase {
+    /**
+     * Test convect fetching.
+     */
+    public function test_concept_fetching() {
+        global $CFG, $DB;
+        $this->resetAfterTest(true);
+        $this->setAdminUser();
+
+        $CFG->glossary_linkbydefault = 1;
+        $CFG->glossary_linkentries = 0;
+
+        // Create a test courses.
+        $course1 = $this->getDataGenerator()->create_course();
+        $course2 = $this->getDataGenerator()->create_course();
+        $site = $DB->get_record('course', array('id' => SITEID));
+
+        // Create a glossary.
+        $glossary1a = $this->getDataGenerator()->create_module('glossary',
+            array('course' => $course1->id, 'mainglossary' => 1, 'usedynalink' => 1));
+        $glossary1b = $this->getDataGenerator()->create_module('glossary',
+            array('course' => $course1->id, 'mainglossary' => 1, 'usedynalink' => 1));
+        $glossary1c = $this->getDataGenerator()->create_module('glossary',
+            array('course' => $course1->id, 'mainglossary' => 1, 'usedynalink' => 0));
+        $glossary2 = $this->getDataGenerator()->create_module('glossary',
+            array('course' => $course2->id, 'mainglossary' => 1, 'usedynalink' => 1));
+        $glossary3 = $this->getDataGenerator()->create_module('glossary',
+            array('course' => $site->id, 'mainglossary' => 1, 'usedynalink' => 1, 'globalglossary' => 1));
+
+        /** @var mod_glossary_generator $generator */
+        $generator = $this->getDataGenerator()->get_plugin_generator('mod_glossary');
+        $entry1a1 = $generator->create_content($glossary1a, array('concept' => 'first', 'usedynalink' => 1), array('prvni', 'erste'));
+        $entry1a2 = $generator->create_content($glossary1a, array('concept' => 'A&B', 'usedynalink' => 1));
+        $entry1a3 = $generator->create_content($glossary1a, array('concept' => 'neee', 'usedynalink' => 0));
+        $entry1b1 = $generator->create_content($glossary1b, array('concept' => 'second', 'usedynalink' => 1));
+        $entry1c1 = $generator->create_content($glossary1c, array('concept' => 'third', 'usedynalink' => 1));
+        $entry31 = $generator->create_content($glossary3, array('concept' => 'global', 'usedynalink' => 1), array('globalni'));
+
+        $cat1 = $generator->create_category($glossary1a, array('name' => 'special'), array($entry1a1, $entry1a2));
+
+        \mod_glossary\local\concept_cache::reset_caches();
+
+        $concepts1 = \mod_glossary\local\concept_cache::get_concepts($course1->id);
+        $this->assertCount(3, $concepts1[0]);
+        $this->arrayHasKey($concepts1[0], $glossary1a->id);
+        $this->arrayHasKey($concepts1[0], $glossary1b->id);
+        $this->arrayHasKey($concepts1[0], $glossary3->id);
+        $this->assertCount(3, $concepts1[1]);
+        $this->arrayHasKey($concepts1[1], $glossary1a->id);
+        $this->arrayHasKey($concepts1[1], $glossary1b->id);
+        $this->arrayHasKey($concepts1[0], $glossary3->id);
+        $this->assertCount(5, $concepts1[1][$glossary1a->id]);
+        foreach($concepts1[1][$glossary1a->id] as $concept) {
+            $this->assertSame(array('id', 'glossaryid', 'concept', 'casesensitive', 'category', 'fullmatch'), array_keys((array)$concept));
+            if ($concept->concept === 'first') {
+                $this->assertEquals($entry1a1->id, $concept->id);
+                $this->assertEquals($glossary1a->id, $concept->glossaryid);
+                $this->assertEquals(0, $concept->category);
+            } else if ($concept->concept === 'prvni') {
+                $this->assertEquals($entry1a1->id, $concept->id);
+                $this->assertEquals($glossary1a->id, $concept->glossaryid);
+                $this->assertEquals(0, $concept->category);
+            } else if ($concept->concept === 'erste') {
+                $this->assertEquals($entry1a1->id, $concept->id);
+                $this->assertEquals($glossary1a->id, $concept->glossaryid);
+                $this->assertEquals(0, $concept->category);
+            } else if ($concept->concept === 'A&amp;B') {
+                $this->assertEquals($entry1a2->id, $concept->id);
+                $this->assertEquals($glossary1a->id, $concept->glossaryid);
+                $this->assertEquals(0, $concept->category);
+            } else if ($concept->concept === 'special') {
+                $this->assertEquals($cat1->id, $concept->id);
+                $this->assertEquals($glossary1a->id, $concept->glossaryid);
+                $this->assertEquals(1, $concept->category);
+            } else {
+                $this->fail('Unexpected concept: ' . $concept->concept);
+            }
+        }
+        $this->assertCount(1, $concepts1[1][$glossary1b->id]);
+        foreach($concepts1[1][$glossary1b->id] as $concept) {
+            $this->assertSame(array('id', 'glossaryid', 'concept', 'casesensitive', 'category', 'fullmatch'), array_keys((array)$concept));
+            if ($concept->concept === 'second') {
+                $this->assertEquals($entry1b1->id, $concept->id);
+                $this->assertEquals($glossary1b->id, $concept->glossaryid);
+                $this->assertEquals(0, $concept->category);
+            } else {
+                $this->fail('Unexpected concept: ' . $concept->concept);
+            }
+        }
+        $this->assertCount(2, $concepts1[1][$glossary3->id]);
+        foreach($concepts1[1][$glossary3->id] as $concept) {
+            $this->assertSame(array('id', 'glossaryid', 'concept', 'casesensitive', 'category', 'fullmatch'), array_keys((array)$concept));
+            if ($concept->concept === 'global') {
+                $this->assertEquals($entry31->id, $concept->id);
+                $this->assertEquals($glossary3->id, $concept->glossaryid);
+                $this->assertEquals(0, $concept->category);
+            } else if ($concept->concept === 'globalni') {
+                $this->assertEquals($entry31->id, $concept->id);
+                $this->assertEquals($glossary3->id, $concept->glossaryid);
+                $this->assertEquals(0, $concept->category);
+            } else {
+                $this->fail('Unexpected concept: ' . $concept->concept);
+            }
+        }
+
+        $concepts3 = \mod_glossary\local\concept_cache::get_concepts($site->id);
+        $this->assertCount(1, $concepts3[0]);
+        $this->arrayHasKey($concepts3[0], $glossary3->id);
+        $this->assertCount(1, $concepts3[1]);
+        $this->arrayHasKey($concepts3[0], $glossary3->id);
+        foreach($concepts3[1][$glossary3->id] as $concept) {
+            $this->assertSame(array('id', 'glossaryid', 'concept', 'casesensitive', 'category', 'fullmatch'), array_keys((array)$concept));
+            if ($concept->concept === 'global') {
+                $this->assertEquals($entry31->id, $concept->id);
+                $this->assertEquals($glossary3->id, $concept->glossaryid);
+                $this->assertEquals(0, $concept->category);
+            } else if ($concept->concept === 'globalni') {
+                $this->assertEquals($entry31->id, $concept->id);
+                $this->assertEquals($glossary3->id, $concept->glossaryid);
+                $this->assertEquals(0, $concept->category);
+            } else {
+                $this->fail('Unexpected concept: ' . $concept->concept);
+            }
+        }
+
+        $concepts2 = \mod_glossary\local\concept_cache::get_concepts($course2->id);
+        $this->assertEquals($concepts3, $concepts2);
+
+        // Test uservisible flag.
+        set_config('enablegroupmembersonly', 1);
+        $glossary1d = $this->getDataGenerator()->create_module('glossary',
+            array('course' => $course1->id, 'mainglossary' => 1, 'usedynalink' => 1, 'groupmembersonly' => 1));
+        $entry1d1 = $generator->create_content($glossary1d, array('concept' => 'membersonly', 'usedynalink' => 1));
+        $user = $this->getDataGenerator()->create_user();
+        $this->getDataGenerator()->enrol_user($user->id, $course1->id);
+        $this->getDataGenerator()->enrol_user($user->id, $course2->id);
+        \mod_glossary\local\concept_cache::reset_caches();
+        $concepts1 = \mod_glossary\local\concept_cache::get_concepts($course1->id);
+        $this->assertCount(4, $concepts1[0]);
+        $this->assertCount(4, $concepts1[1]);
+        $this->setUser($user);
+        course_modinfo::clear_instance_cache();
+        \mod_glossary\local\concept_cache::reset_caches();
+        $concepts1 = \mod_glossary\local\concept_cache::get_concepts($course1->id);
+        $this->assertCount(3, $concepts1[0]);
+        $this->assertCount(3, $concepts1[1]);
+    }
+}
index aca0e9a..fd7c452 100644 (file)
@@ -40,6 +40,11 @@ class mod_glossary_generator extends testing_module_generator {
      */
     protected $entrycount = 0;
 
+    /**
+     * @var int keep track of how many entries have been created.
+     */
+    protected $categorycount = 0;
+
     /**
      * To be called from data reset code only,
      * do not use in tests.
@@ -47,6 +52,7 @@ class mod_glossary_generator extends testing_module_generator {
      */
     public function reset() {
         $this->entrycount = 0;
+        $this->categorycount = 0;
         parent::reset();
     }
 
@@ -77,7 +83,30 @@ class mod_glossary_generator extends testing_module_generator {
         return parent::create_instance($record, (array)$options);
     }
 
-    public function create_content($glossary, $record = array()) {
+    public function create_category($glossary, $record = array(), $entries = array()) {
+        global $CFG, $DB;
+        $this->categorycount++;
+        $record = (array)$record + array(
+            'name' => 'Glossary category '.$this->categorycount,
+            'usedynalink' => $CFG->glossary_linkbydefault,
+        );
+        $record['glossaryid'] = $glossary->id;
+
+        $id = $DB->insert_record('glossary_categories', $record);
+
+        if ($entries) {
+            foreach ($entries as $entry) {
+                $ce = new stdClass();
+                $ce->categoryid = $id;
+                $ce->entryid = $entry->id;
+                $DB->insert_record('glossary_entries_categories', $ce);
+            }
+        }
+
+        return $DB->get_record('glossary_categories', array('id' => $id), '*', MUST_EXIST);
+    }
+
+    public function create_content($glossary, $record = array(), $aliases = array()) {
         global $DB, $USER, $CFG;
         $this->entrycount++;
         $now = time();
@@ -106,6 +135,16 @@ class mod_glossary_generator extends testing_module_generator {
         }
 
         $id = $DB->insert_record('glossary_entries', $record);
+
+        if ($aliases) {
+            foreach ($aliases as $alias) {
+                $ar = new stdClass();
+                $ar->entryid = $id;
+                $ar->alias = $alias;
+                $DB->insert_record('glossary_alias', $ar);
+            }
+        }
+
         return $DB->get_record('glossary_entries', array('id' => $id), '*', MUST_EXIST);
     }
 }
index b00fef4..d6df92f 100644 (file)
@@ -43,13 +43,13 @@ class mod_glossary_generator_testcase extends advanced_testcase {
         $this->assertFalse($DB->record_exists('glossary', array('course' => $course->id)));
         $glossary = $this->getDataGenerator()->create_module('glossary', array('course' => $course));
         $records = $DB->get_records('glossary', array('course' => $course->id), 'id');
-        $this->assertEquals(1, count($records));
+        $this->assertCount(1, $records);
         $this->assertTrue(array_key_exists($glossary->id, $records));
 
         $params = array('course' => $course->id, 'name' => 'Another glossary');
         $glossary = $this->getDataGenerator()->create_module('glossary', $params);
         $records = $DB->get_records('glossary', array('course' => $course->id), 'id');
-        $this->assertEquals(2, count($records));
+        $this->assertCount(2, $records);
         $this->assertEquals('Another glossary', $records[$glossary->id]->name);
     }
 
@@ -60,14 +60,30 @@ class mod_glossary_generator_testcase extends advanced_testcase {
 
         $course = $this->getDataGenerator()->create_course();
         $glossary = $this->getDataGenerator()->create_module('glossary', array('course' => $course));
+        /** @var mod_glossary_generator $glossarygenerator */
         $glossarygenerator = $this->getDataGenerator()->get_plugin_generator('mod_glossary');
 
         $entry1 = $glossarygenerator->create_content($glossary);
-        $entry2 = $glossarygenerator->create_content($glossary, array('concept' => 'Custom concept'));
+        $entry2 = $glossarygenerator->create_content($glossary, array('concept' => 'Custom concept'), array('alias1', 'alias2'));
         $records = $DB->get_records('glossary_entries', array('glossaryid' => $glossary->id), 'id');
-        $this->assertEquals(2, count($records));
+        $this->assertCount(2, $records);
         $this->assertEquals($entry1->id, $records[$entry1->id]->id);
         $this->assertEquals($entry2->id, $records[$entry2->id]->id);
         $this->assertEquals('Custom concept', $records[$entry2->id]->concept);
+        $aliases = $DB->get_records_menu('glossary_alias', array('entryid' => $entry2->id), 'id ASC', 'id, alias');
+        $this->assertSame(array('alias1', 'alias2'), array_values($aliases));
+
+        // Test adding of category to entry.
+        $categories = $DB->get_records('glossary_categories', array('glossaryid' => $glossary->id));
+        $this->assertCount(0, $categories);
+        $entry3 = $glossarygenerator->create_content($glossary, array('concept' => 'In category'));
+        $category1 = $glossarygenerator->create_category($glossary, array());
+        $categories = $DB->get_records('glossary_categories', array('glossaryid' => $glossary->id));
+        $this->assertCount(1, $categories);
+        $category2 = $glossarygenerator->create_category($glossary, array('name' => 'Some category'), array($entry2, $entry3));
+        $categories = $DB->get_records('glossary_categories', array('glossaryid' => $glossary->id));
+        $this->assertCount(2, $categories);
+        $members = $DB->get_records_menu('glossary_entries_categories', array('categoryid' => $category2->id), 'id ASC', 'id, entryid');
+        $this->assertSame(array($entry2->id, $entry3->id), array_values($members));
     }
 }
index 4673969..a286e47 100644 (file)
@@ -24,7 +24,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2013110500;       // The current module version (Date: YYYYMMDDXX)
-$plugin->requires  = 2013110500;    // Requires this Moodle version
+$plugin->version   = 2014040600;       // The current module version (Date: YYYYMMDDXX)
+$plugin->requires  = 2014040300;    // Requires this Moodle version
 $plugin->component = 'mod_glossary';   // Full name of the plugin (used for diagnostics)
 $plugin->cron      = 0;
index 0e761e1..8903475 100644 (file)
@@ -95,6 +95,10 @@ function lti_add_instance($lti, $mform) {
     $lti->timemodified = $lti->timecreated;
     $lti->servicesalt = uniqid('', true);
 
+    if (empty($lti->typeid) && isset($lti->urlmatchedtypeid)) {
+        $lti->typeid = $lti->urlmatchedtypeid;
+    }
+
     if (!isset($lti->grade)) {
         $lti->grade = 100; // TODO: Why is this harcoded here and default @ DB
     }
@@ -145,6 +149,10 @@ function lti_update_instance($lti, $mform) {
         lti_grade_item_delete($lti);
     }
 
+    if ($lti->typeid == 0 && isset($lti->urlmatchedtypeid)) {
+        $lti->typeid = $lti->urlmatchedtypeid;
+    }
+
     return $DB->update_record('lti', $lti);
 }
 
index ace191a..aabce00 100644 (file)
                             self.toolTypeCache[selectedToolType] = toolInfo;
                         }
 
+                        Y.one('#id_urlmatchedtypeid').set('value', toolInfo.toolid);
+
                         continuation(toolInfo);
                     }
                 });
index 67b1cfc..c232268 100644 (file)
@@ -118,6 +118,9 @@ class mod_lti_mod_form extends moodleform_mod {
         $mform->setAdvanced('securetoolurl');
         $mform->addHelpButton('securetoolurl', 'secure_launch_url', 'lti');
 
+        $mform->addElement('hidden', 'urlmatchedtypeid', '', array( 'id' => 'id_urlmatchedtypeid' ));
+        $mform->setType('urlmatchedtypeid', PARAM_INT);
+
         $launchoptions=array();
         $launchoptions[LTI_LAUNCH_CONTAINER_DEFAULT] = get_string('default', 'lti');
         $launchoptions[LTI_LAUNCH_CONTAINER_EMBED] = get_string('embed', 'lti');
index 9646958..4b12d21 100644 (file)
@@ -89,10 +89,8 @@ if ($data = $mform->get_data()) {
         list($parentid, $contextid) = explode(',', $data->parent);
         $categoryid = $qcobject->add_category($data->parent, $data->name, '', true);
         $includesubcategories = 0;
-        add_to_log($quiz->course, 'quiz', 'addcategory',
-                'view.php?id=' . $cm->id, $categoryid, $cm->id);
-        $returnurl->param('cat', $categoryid . ',' . $contextid);
 
+        $returnurl->param('cat', $categoryid . ',' . $contextid);
     } else {
         throw new coding_exception(
                 'It seems a form was submitted without any button being pressed???');
index d7a5b1b..181c9bc 100644 (file)
@@ -95,9 +95,18 @@ if ($autosaveperiod) {
 }
 
 // Log this page view.
-add_to_log($attemptobj->get_courseid(), 'quiz', 'continue attempt',
-        'review.php?attempt=' . $attemptobj->get_attemptid(),
-        $attemptobj->get_quizid(), $attemptobj->get_cmid());
+$params = array(
+    'objectid' => $attemptid,
+    'relateduserid' => $attemptobj->get_userid(),
+    'courseid' => $attemptobj->get_courseid(),
+    'context' => context_module::instance($attemptobj->get_cmid()),
+    'other' => array(
+        'quizid' => $attemptobj->get_quizid()
+    )
+);
+$event = \mod_quiz\event\attempt_viewed::create($params);
+$event->add_record_snapshot('quiz_attempts', $attemptobj->get_attempt());
+$event->trigger();
 
 // Get the list of questions needed by this page.
 $slots = $attemptobj->get_slots($page);
diff --git a/mod/quiz/classes/event/attempt_deleted.php b/mod/quiz/classes/event/attempt_deleted.php
new file mode 100644 (file)
index 0000000..7b4f9d3
--- /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/>.
+
+/**
+ * Attempt deleted event class.
+ *
+ * @package    mod_quiz
+ * @since      Moodle 2.7
+ * @copyright  2014 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace mod_quiz\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+class attempt_deleted extends \core\event\base {
+
+    /**
+     * Init method.
+     */
+    protected function init() {
+        $this->data['objecttable'] = 'quiz_attempts';
+        $this->data['crud'] = 'd';
+        $this->data['edulevel'] = self::LEVEL_TEACHING;
+    }
+
+    /**
+     * Returns localised general event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('eventattemptdeleted', 'mod_quiz');
+    }
+
+    /**
+     * Returns description of what happened.
+     *
+     * @return string
+     */
+    public function get_description() {
+        return 'A quiz attempt with the id of ' . $this->objectid . ' belonging to the quiz with the id ' . $this->other['quizid'] .
+            ' for the user with the id ' . $this->relateduserid . ' was deleted by a user with the id ' . $this->userid;
+    }
+
+    /**
+     * Returns relevant URL.
+     *
+     * @return \moodle_url
+     */
+    public function get_url() {
+        return new \moodle_url('/mod/quiz/report.php', array('id' => $this->contextinstanceid));
+    }
+
+    /**
+     * Return the legacy event log data.
+     *
+     * @return array
+     */
+    protected function get_legacy_logdata() {
+        return array($this->courseid, 'quiz', 'delete attempt', 'report.php?id=' . $this->contextinstanceid,
+            $this->objectid, $this->contextinstanceid);
+    }
+
+    /**
+     * Custom validation.
+     *
+     * @throws \coding_exception
+     * @return void
+     */
+    protected function validate_data() {
+        parent::validate_data();
+
+        if (!isset($this->relateduserid)) {
+            throw new \coding_exception('The \'relateduserid\' must be set.');
+        }
+
+        if (!isset($this->other['quizid'])) {
+            throw new \coding_exception('The \'quizid\' must be set in other.');
+        }
+    }
+}
diff --git a/mod/quiz/classes/event/attempt_preview_started.php b/mod/quiz/classes/event/attempt_preview_started.php
new file mode 100644 (file)
index 0000000..fc352a2
--- /dev/null
@@ -0,0 +1,101 @@
+<?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/>.
+
+/**
+ * Attempt preview started event class.
+ *
+ * @property-read array $other {
+ *      Extra information about event.
+ *
+ *      - int quizid: the id of the quiz.
+ * }
+ *
+ * @package    mod_quiz
+ * @since      Moodle 2.7
+ * @copyright  2014 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace mod_quiz\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+class attempt_preview_started extends \core\event\base {
+
+    /**
+     * Init method.
+     */
+    protected function init() {
+        $this->data['objecttable'] = 'quiz_attempts';
+        $this->data['crud'] = 'r';
+        $this->data['edulevel'] = self::LEVEL_TEACHING;
+    }
+
+    /**
+     * Returns localised general event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('eventattemptpreviewstarted', 'mod_quiz');
+    }
+
+    /**
+     * Returns description of what happened.
+     *
+     * @return string
+     */
+    public function get_description() {
+        return 'A quiz attempt with the id of ' . $this->objectid . ' belonging to the quiz with the id ' . $this->other['quizid'] .
+            ' was previewed by the user with the id ' . $this->relateduserid;
+    }
+
+    /**
+     * Returns relevant URL.
+     *
+     * @return \moodle_url
+     */
+    public function get_url() {
+        return new \moodle_url('/mod/quiz/view.php', array('id' => $this->contextinstanceid));
+    }
+
+    /**
+     * Return the legacy event log data.
+     *
+     * @return array
+     */
+    protected function get_legacy_logdata() {
+        return array($this->courseid, 'quiz', 'preview', 'view.php?id=' . $this->contextinstanceid,
+            $this->other['quizid'],  $this->contextinstanceid);
+    }
+
+    /**
+     * Custom validation.
+     *
+     * @throws \coding_exception
+     * @return void
+     */
+    protected function validate_data() {
+        parent::validate_data();
+
+        if (!isset($this->relateduserid)) {
+            throw new \coding_exception('The \'relateduserid\' must be set.');
+        }
+
+        if (!isset($this->other['quizid'])) {
+            throw new \coding_exception('The \'quizid\' must be set in other.');
+        }
+    }
+}
diff --git a/mod/quiz/classes/event/attempt_reviewed.php b/mod/quiz/classes/event/attempt_reviewed.php
new file mode 100644 (file)
index 0000000..dd46467
--- /dev/null
@@ -0,0 +1,101 @@
+<?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/>.
+
+/**
+ * Attempt reviewed event class.
+ *
+ * @property-read array $other {
+ *      Extra information about event.
+ *
+ *      - int quizid: the id of the quiz.
+ * }
+ *
+ * @package    mod_quiz
+ * @since      Moodle 2.7
+ * @copyright  2014 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace mod_quiz\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+class attempt_reviewed extends \core\event\base {
+
+    /**
+     * Init method.
+     */
+    protected function init() {
+        $this->data['objecttable'] = 'quiz_attempts';
+        $this->data['crud'] = 'r';
+        $this->data['edulevel'] = self::LEVEL_TEACHING;
+    }
+
+    /**
+     * Returns localised general event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('eventattemptreviewed', 'mod_quiz');
+    }
+
+    /**
+     * Returns description of what happened.
+     *
+     * @return string
+     */
+    public function get_description() {
+        return 'A quiz attempt with the id of ' . $this->objectid . ' for the quiz with the id ' . $this->other['quizid'] .
+            ' belonging to a user with the id ' . $this->relateduserid . ' was reviewed by a user with the id ' . $this->userid;
+    }
+
+    /**
+     * Returns relevant URL.
+     *
+     * @return \moodle_url
+     */
+    public function get_url() {
+        return new \moodle_url('/mod/quiz/review.php', array('attempt' => $this->objectid));
+    }
+
+    /**
+     * Return the legacy event log data.
+     *
+     * @return array
+     */
+    protected function get_legacy_logdata() {
+        return array($this->courseid, 'quiz', 'review', 'review.php?attempt=' . $this->objectid,
+            $this->other['quizid'], $this->contextinstanceid);
+    }
+
+    /**
+     * Custom validation.
+     *
+     * @throws \coding_exception
+     * @return void
+     */
+    protected function validate_data() {
+        parent::validate_data();
+
+        if (!isset($this->relateduserid)) {
+            throw new \coding_exception('The \'relateduserid\' must be set.');
+        }
+
+        if (!isset($this->other['quizid'])) {
+            throw new \coding_exception('The \'quizid\' must be set in other.');
+        }
+    }
+}
index 69ba87d..fa641c9 100644 (file)
@@ -100,6 +100,18 @@ class attempt_started extends \core\event\base {
         return $legacyeventdata;
     }
 
+    /**
+     * Return the legacy event log data.
+     *
+     * @return array
+     */
+    protected function get_legacy_logdata() {
+        $attempt = $this->get_record_snapshot('quiz_attempts', $this->objectid);
+
+        return array($this->courseid, 'quiz', 'attempt', 'review.php?attempt=' . $this->objectid,
+            $attempt->quiz, $this->contextinstanceid);
+    }
+
     /**
      * Custom validation.
      *
diff --git a/mod/quiz/classes/event/attempt_summary_viewed.php b/mod/quiz/classes/event/attempt_summary_viewed.php
new file mode 100644 (file)
index 0000000..2b110ad
--- /dev/null
@@ -0,0 +1,104 @@
+<?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/>.
+
+/**
+ * Attempt summary viewed event.
+ *
+ * @property-read array $other {
+ *      Extra information about event.
+ *
+ *      - int quizid: the id of the quiz.
+ * }
+ *
+ * @package    mod_quiz
+ * @since      Moodle 2.7
+ * @copyright  2014 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace mod_quiz\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+class attempt_summary_viewed extends \core\event\base {
+
+    /**
+     * Init method.
+     *
+     * @return void
+     */
+    protected function init() {
+        $this->data['objecttable'] = 'quiz_attempts';
+        $this->data['crud'] = 'r';
+        $this->data['edulevel'] = self::LEVEL_TEACHING;
+    }
+
+    /**
+     * Return localised event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('eventattemptsummaryviewed', 'mod_quiz');
+    }
+
+    /**
+     * Returns description of what happened.
+     *
+     * @return string
+     */
+    public function get_description() {
+        return 'The summary for the quiz attempt with the id of ' . $this->objectid . ' belonging to a user with the id ' .
+            $this->relateduserid . ' was viewed by a user with the id ' . $this->userid;
+    }
+
+    /**
+     * Get URL related to the action.
+     *
+     * @return \moodle_url
+     */
+    public function get_url() {
+        return new \moodle_url('/mod/quiz/summary.php', array('attempt' => $this->objectid));
+    }
+
+    /**
+     * Return the legacy event log data.
+     *
+     * @return array
+     */
+    protected function get_legacy_logdata() {
+        return array($this->courseid, 'quiz', 'view summary', 'summary.php?attempt=' . $this->objectid,
+            $this->other['quizid'], $this->contextinstanceid);
+    }
+
+    /**
+     * Custom validation.
+     *
+     * @throws \coding_exception
+     * @return void
+     */
+    protected function validate_data() {
+        parent::validate_data();
+
+        if (!isset($this->relateduserid)) {
+            throw new \coding_exception('The \'relateduserid\' must be set.');
+        }
+
+        if (!isset($this->other['quizid'])) {
+            throw new \coding_exception('The \'quizid\' must be set in other.');
+        }
+    }
+}
diff --git a/mod/quiz/classes/event/attempt_viewed.php b/mod/quiz/classes/event/attempt_viewed.php
new file mode 100644 (file)
index 0000000..a9620ff
--- /dev/null
@@ -0,0 +1,101 @@
+<?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/>.
+
+/**
+ * Attempt viewed event class.
+ *
+ * @property-read array $other {
+ *      Extra information about event.
+ *
+ *      - int quizid: the id of the quiz.
+ * }
+ *
+ * @package    mod_quiz
+ * @since      Moodle 2.7
+ * @copyright  2014 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace mod_quiz\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+class attempt_viewed extends \core\event\base {
+
+    /**
+     * Init method.
+     */
+    protected function init() {
+        $this->data['objecttable'] = 'quiz_attempts';
+        $this->data['crud'] = 'r';
+        $this->data['edulevel'] = self::LEVEL_PARTICIPATING;
+    }
+
+    /**
+     * Returns localised general event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('eventattemptviewed', 'mod_quiz');
+    }
+
+    /**
+     * Returns description of what happened.
+     *
+     * @return string
+     */
+    public function get_description() {
+        return 'A quiz attempt with the id of ' . $this->objectid . ' belonging to the quiz with the id ' . $this->other['quizid'] .
+            ' was viewed by the user with the id ' . $this->relateduserid;
+    }
+
+    /**
+     * Returns relevant URL.
+     *
+     * @return \moodle_url
+     */
+    public function get_url() {
+        return new \moodle_url('/mod/quiz/review.php', array('attempt' => $this->objectid));
+    }
+
+    /**
+     * Return the legacy event log data.
+     *
+     * @return array
+     */
+    protected function get_legacy_logdata() {
+        return array($this->courseid, 'quiz', 'continue attempt', 'review.php?attempt=' . $this->objectid,
+            $this->other['quizid'], $this->contextinstanceid);
+    }
+
+    /**
+     * Custom validation.
+     *
+     * @throws \coding_exception
+     * @return void
+     */
+    protected function validate_data() {
+        parent::validate_data();
+
+        if (!isset($this->relateduserid)) {
+            throw new \coding_exception('The \'relateduserid\' must be set.');
+        }
+
+        if (!isset($this->other['quizid'])) {
+            throw new \coding_exception('The \'quizid\' must be set in other.');
+        }
+    }
+}
diff --git a/mod/quiz/classes/event/course_module_instance_list_viewed.php b/mod/quiz/classes/event/course_module_instance_list_viewed.php
new file mode 100644 (file)
index 0000000..1218cff
--- /dev/null
@@ -0,0 +1,32 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * The instance list viewed event.
+ *
+ * @package    mod_quiz
+ * @since      Moodle 2.7
+ * @copyright  2014 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace mod_quiz\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+class course_module_instance_list_viewed extends \core\event\course_module_instance_list_viewed {
+    // No code required here as the parent class handles it all.
+}
diff --git a/mod/quiz/classes/event/course_module_viewed.php b/mod/quiz/classes/event/course_module_viewed.php
new file mode 100644 (file)
index 0000000..4a1c3bb
--- /dev/null
@@ -0,0 +1,42 @@
+<?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 module viewed event.
+ *
+ * @package    mod_quiz
+ * @since      Moodle 2.7
+ * @copyright  2014 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace mod_quiz\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+class course_module_viewed extends \core\event\course_module_viewed {
+
+    /**
+     * Init method.
+     *
+     * @return void
+     */
+    protected function init() {
+        $this->data['crud'] = 'r';
+        $this->data['edulevel'] = self::LEVEL_PARTICIPATING;
+        $this->data['objecttable'] = 'quiz';
+    }
+}
diff --git a/mod/quiz/classes/event/edit_page_viewed.php b/mod/quiz/classes/event/edit_page_viewed.php
new file mode 100644 (file)
index 0000000..45a9501
--- /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/>.
+
+/**
+ * Edit page viewed event class.
+ *
+ * @property-read array $other {
+ *      Extra information about event.
+ *
+ *      - int quizid: the id of the quiz.
+ * }
+ *
+ * @package    mod_quiz
+ * @since      Moodle 2.7
+ * @copyright  2014 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace mod_quiz\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+class edit_page_viewed extends \core\event\base {
+
+    /**
+     * Init method.
+     */
+    protected function init() {
+        $this->data['crud'] = 'r';
+        $this->data['edulevel'] = self::LEVEL_TEACHING;
+    }
+
+    /**
+     * Returns localised general event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('eventeditpageviewed', 'mod_quiz');
+    }
+
+    /**
+     * Returns description of what happened.
+     *
+     * @return string
+     */
+    public function get_description() {
+        return 'The edit quiz page belonging to the quiz with the id ' . $this->other['quizid'] . ' was viewed by the user
+            with the id ' . $this->userid;
+    }
+
+    /**
+     * Returns relevant URL.
+     *
+     * @return \moodle_url
+     */
+    public function get_url() {
+        return new \moodle_url('/mod/quiz/edit.php', array('cmid' => $this->contextinstanceid));
+    }
+
+    /**
+     * Return the legacy event log data.
+     *
+     * @return array
+     */
+    protected function get_legacy_logdata() {
+        return array($this->courseid, 'quiz', 'editquestions', 'view.php?id=' . $this->contextinstanceid,
+            $this->other['quizid'], $this->contextinstanceid);
+    }
+
+    /**
+     * Custom validation.
+     *
+     * @throws \coding_exception
+     * @return void
+     */
+    protected function validate_data() {
+        parent::validate_data();
+
+        if (!isset($this->other['quizid'])) {
+            throw new \coding_exception('The \'quizid\' must be set in other.');
+        }
+    }
+}
diff --git a/mod/quiz/classes/event/group_override_created.php b/mod/quiz/classes/event/group_override_created.php
new file mode 100644 (file)
index 0000000..cac8c89
--- /dev/null
@@ -0,0 +1,92 @@
+<?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/>.
+
+/**
+ * Group override created event class.
+ *
+ * @property-read array $other {
+ *      Extra information about event.
+ *
+ *      - int quizid: the id of the quiz.
+ *      - int groupid: the id of the group.
+ * }
+ *
+ * @package    mod_quiz
+ * @since      Moodle 2.7
+ * @copyright  2014 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace mod_quiz\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+class group_override_created extends \core\event\base {
+
+    /**
+     * Init method.
+     */
+    protected function init() {
+        $this->data['objecttable'] = 'quiz_overrides';
+        $this->data['crud'] = 'c';
+        $this->data['edulevel'] = self::LEVEL_TEACHING;
+    }
+
+    /**
+     * Returns localised general event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('eventoverridecreated', 'mod_quiz');
+    }
+
+    /**
+     * Returns description of what happened.
+     *
+     * @return string
+     */
+    public function get_description() {
+        return 'An override with the id ' . $this->objectid . ' for the quiz with the id of ' . $this->other['quizid'] .
+            ' was created by the user with the id ' . $this->userid . ' for the group with the id ' . $this->other['groupid'];
+    }
+
+    /**
+     * Returns relevant URL.
+     *
+     * @return \moodle_url
+     */
+    public function get_url() {
+        return new \moodle_url('/mod/quiz/overrideedit.php', array('id' => $this->objectid));
+    }
+
+    /**
+     * Custom validation.
+     *
+     * @throws \coding_exception
+     * @return void
+     */
+    protected function validate_data() {
+        parent::validate_data();
+
+        if (!isset($this->other['quizid'])) {
+            throw new \coding_exception('The \'quizid\' must be set in other.');
+        }
+
+        if (!isset($this->other['groupid'])) {
+            throw new \coding_exception('The \'groupid\' must be set in other.');
+        }
+    }
+}
diff --git a/mod/quiz/classes/event/group_override_deleted.php b/mod/quiz/classes/event/group_override_deleted.php
new file mode 100644 (file)
index 0000000..f1304bb
--- /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/>.
+
+/**
+ * Group override deleted event class.
+ *
+ * @property-read array $other {
+ *      Extra information about event.
+ *
+ *      - int quizid: the id of the quiz.
+ *      - int groupid: the id of the group.
+ * }
+ *
+ * @package    mod_quiz
+ * @since      Moodle 2.7
+ * @copyright  2014 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace mod_quiz\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+class group_override_deleted extends \core\event\base {
+
+    /**
+     * Init method.
+     */
+    protected function init() {
+        $this->data['objecttable'] = 'quiz_overrides';
+        $this->data['crud'] = 'd';
+        $this->data['edulevel'] = self::LEVEL_TEACHING;
+    }
+
+    /**
+     * Returns localised general event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('eventoverridedeleted', 'mod_quiz');
+    }
+
+    /**
+     * Returns description of what happened.
+     *
+     * @return string
+     */
+    public function get_description() {
+        return 'An override with the id ' . $this->objectid . ' for the quiz with the id of ' . $this->other['quizid'] .
+            ' was deleted by the user with the id ' . $this->userid . ' for the group with the id ' . $this->other['groupid'];
+    }
+
+    /**
+     * Returns relevant URL.
+     *
+     * @return \moodle_url
+     */
+    public function get_url() {
+        return new \moodle_url('/mod/quiz/overrides.php', array('cmid' => $this->contextinstanceid));
+    }
+
+    /**
+     * Return the legacy event log data.
+     *
+     * @return array
+     */
+    protected function get_legacy_logdata() {
+        return array($this->courseid, 'quiz', 'delete override', 'overrides.php?cmid=' . $this->contextinstanceid,
+            $this->other['quizid'], $this->contextinstanceid);
+    }
+
+    /**
+     * Custom validation.
+     *
+     * @throws \coding_exception
+     * @return void
+     */
+    protected function validate_data() {
+        parent::validate_data();
+
+        if (!isset($this->other['quizid'])) {
+            throw new \coding_exception('The \'quizid\' must be set in other.');
+        }
+
+        if (!isset($this->other['groupid'])) {
+            throw new \coding_exception('The \'groupid\' must be set in other.');
+        }
+    }
+}
diff --git a/mod/quiz/classes/event/group_override_updated.php b/mod/quiz/classes/event/group_override_updated.php
new file mode 100644 (file)
index 0000000..24942c4
--- /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/>.
+
+/**
+ * Group override updated event class.
+ *
+ * @property-read array $other {
+ *      Extra information about event.
+ *
+ *      - int quizid: the id of the quiz.
+ *      - int groupid: the id of the group.
+ * }
+ *
+ * @package    mod_quiz
+ * @since      Moodle 2.7
+ * @copyright  2014 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace mod_quiz\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+class group_override_updated extends \core\event\base {
+
+    /**
+     * Init method.
+     */
+    protected function init() {
+        $this->data['objecttable'] = 'quiz_overrides';
+        $this->data['crud'] = 'u';
+        $this->data['edulevel'] = self::LEVEL_TEACHING;
+    }
+
+    /**
+     * Returns localised general event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('eventoverrideupdated', 'mod_quiz');
+    }
+
+    /**
+     * Returns description of what happened.
+     *
+     * @return string
+     */
+    public function get_description() {
+        return 'An override with the id ' . $this->objectid . ' for the quiz with the id of ' . $this->other['quizid'] .
+            ' was updated by the user with the id ' . $this->userid . ' for the group with the id ' . $this->other['groupid'];
+    }
+
+    /**
+     * Returns relevant URL.
+     *
+     * @return \moodle_url
+     */
+    public function get_url() {
+        return new \moodle_url('/mod/quiz/overrideedit.php', array('id' => $this->objectid));
+    }
+
+    /**
+     * Return the legacy event log data.
+     *
+     * @return array
+     */
+    protected function get_legacy_logdata() {
+        return array($this->courseid, 'quiz', 'edit override', 'overrideedit.php?id=' . $this->objectid, $this->other['quizid'],
+            $this->contextinstanceid);
+    }
+
+    /**
+     * Custom validation.
+     *
+     * @throws \coding_exception
+     * @return void
+     */
+    protected function validate_data() {
+        parent::validate_data();
+
+        if (!isset($this->other['quizid'])) {
+            throw new \coding_exception('The \'quizid\' must be set in other.');
+        }
+
+        if (!isset($this->other['groupid'])) {
+            throw new \coding_exception('The \'groupid\' must be set in other.');
+        }
+    }
+}
diff --git a/mod/quiz/classes/event/question_manually_graded.php b/mod/quiz/classes/event/question_manually_graded.php
new file mode 100644 (file)
index 0000000..d6444dc
--- /dev/null
@@ -0,0 +1,109 @@
+<?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/>.
+
+/**
+ * Question manually graded event class.
+ *
+ * @property-read array $other {
+ *      Extra information about event.
+ *
+ *      - int quizid: the id of the quiz.
+ *      - int attemptid: the id of the attempt.
+ *      - int slot: the question number in the attempt.
+ * }
+ *
+ * @package    core
+ * @since      Moodle 2.7
+ * @copyright  2014 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace mod_quiz\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+class question_manually_graded extends \core\event\base {
+
+    /**
+     * Init method.
+     */
+    protected function init() {
+        $this->data['objecttable'] = 'question';
+        $this->data['crud'] = 'c';
+        $this->data['edulevel'] = self::LEVEL_TEACHING;
+    }
+
+    /**
+     * Returns localised general event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('eventquestionmanuallygraded', 'mod_quiz');
+    }
+
+    /**
+     * Returns description of what happened.
+     *
+     * @return string
+     */
+    public function get_description() {
+        return 'A question with the id of ' . $this->objectid . ' was manually graded by a user with the id ' . $this->userid .
+            ' for the attempt with the id ' . $this->other['attemptid'];
+    }
+
+    /**
+     * Returns relevant URL.
+     *
+     * @return \moodle_url
+     */
+    public function get_url() {
+        return new \moodle_url('/mod/quiz/comment.php', array('attempt' => $this->other['attemptid'],
+            'slot' => $this->other['slot']));
+    }
+
+    /**
+     * Return the legacy event log data.
+     *
+     * @return array
+     */
+    protected function get_legacy_logdata() {
+        return array($this->courseid, 'quiz', 'manualgrade', 'comment.php?attempt=' . $this->other['attemptid'] .
+            '&slot=' . $this->other['slot'], $this->other['quizid'], $this->contextinstanceid);
+    }
+
+    /**
+     * Custom validation.
+     *
+     * @throws \coding_exception
+     * @return void
+     */
+    protected function validate_data() {
+        parent::validate_data();
+
+        if (!isset($this->other['quizid'])) {
+            throw new \coding_exception('The \'quizid\' must be set in other.');
+        }
+
+        if (!isset($this->other['attemptid'])) {
+            throw new \coding_exception('The \'attemptid\' must be set in other.');
+        }
+
+        if (!isset($this->other['slot'])) {
+            throw new \coding_exception('The \'slot\' must be set in other.');
+        }
+
+    }
+}
diff --git a/mod/quiz/classes/event/report_viewed.php b/mod/quiz/classes/event/report_viewed.php
new file mode 100644 (file)
index 0000000..da2d301
--- /dev/null
@@ -0,0 +1,105 @@
+<?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/>.
+
+/**
+ * Report viewed event.
+ *
+ * @property-read array $other {
+ *      Extra information about event.
+ *
+ *      - int quizid: the id of the quiz.
+ *      - string reportname: the name of the report.
+ * }
+ *
+ * @package    mod_quiz
+ * @since      Moodle 2.7
+ * @copyright  2014 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace mod_quiz\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+class report_viewed extends \core\event\base {
+
+    /**
+     * Init method.
+     *
+     * @return void
+     */
+    protected function init() {
+        $this->data['crud'] = 'r';
+        $this->data['edulevel'] = self::LEVEL_TEACHING;
+    }
+
+    /**
+     * Return localised event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('eventreportviewed', 'mod_quiz');
+    }
+
+    /**
+     * Returns description of what happened.
+     *
+     * @return string
+     */
+    public function get_description() {
+        return 'User with id ' . $this->userid . ' viewed the report \'' . s($this->other['reportname']) . '\' for the quiz
+            with the id ' . $this->other['quizid'];
+    }
+
+    /**
+     * Get URL related to the action.
+     *
+     * @return \moodle_url
+     */
+    public function get_url() {
+        return new \moodle_url('/mod/quiz/report.php', array('id' => $this->contextinstanceid,
+            'mode' => $this->other['reportname']));
+    }
+
+    /**
+     * Return the legacy event log data.
+     *
+     * @return array
+     */
+    protected function get_legacy_logdata() {
+        return array($this->courseid, 'quiz', 'report', 'report.php?id=' . $this->contextinstanceid . '&mode=' .
+            $this->other['reportname'], $this->other['quizid'], $this->contextinstanceid);
+    }
+
+    /**
+     * Custom validation.
+     *
+     * @throws \coding_exception
+     * @return void
+     */
+    protected function validate_data() {
+        parent::validate_data();
+
+        if (!isset($this->other['quizid'])) {
+            throw new \coding_exception('The \'quizid\' must be set in other.');
+        }
+
+        if (!isset($this->other['reportname'])) {
+            throw new \coding_exception('The \'reportname\' must be set in other.');
+        }
+    }
+}
diff --git a/mod/quiz/classes/event/user_override_created.php b/mod/quiz/classes/event/user_override_created.php
new file mode 100644 (file)
index 0000000..3a1eafb
--- /dev/null
@@ -0,0 +1,91 @@
+<?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 override created event class.
+ *
+ * @property-read array $other {
+ *      Extra information about event.
+ *
+ *      - int quizid: the id of the quiz.
+ * }
+ *
+ * @package    mod_quiz
+ * @since      Moodle 2.7
+ * @copyright  2014 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace mod_quiz\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+class user_override_created extends \core\event\base {
+
+    /**
+     * Init method.
+     */
+    protected function init() {
+        $this->data['objecttable'] = 'quiz_overrides';
+        $this->data['crud'] = 'c';
+        $this->data['edulevel'] = self::LEVEL_TEACHING;
+    }
+
+    /**
+     * Returns localised general event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('eventoverridecreated', 'mod_quiz');
+    }
+
+    /**
+     * Returns description of what happened.
+     *
+     * @return string
+     */
+    public function get_description() {
+        return 'An override with the id ' . $this->objectid . ' for the quiz with the id of ' . $this->other['quizid'] .
+            ' was created by the user with the id ' . $this->userid . ' for the user with the id ' . $this->relateduserid;
+    }
+
+    /**
+     * Returns relevant URL.
+     *
+     * @return \moodle_url
+     */
+    public function get_url() {
+        return new \moodle_url('/mod/quiz/overrideedit.php', array('id' => $this->objectid));
+    }
+
+    /**
+     * Custom validation.
+     *
+     * @throws \coding_exception
+     * @return void
+     */
+    protected function validate_data() {
+        parent::validate_data();
+
+        if (!isset($this->relateduserid)) {
+            throw new \coding_exception('The \'relateduserid\' must be set.');
+        }
+
+        if (!isset($this->other['quizid'])) {
+            throw new \coding_exception('The \'quizid\' must be set in other.');
+        }
+    }
+}
diff --git a/mod/quiz/classes/event/user_override_deleted.php b/mod/quiz/classes/event/user_override_deleted.php
new file mode 100644 (file)
index 0000000..b441593
--- /dev/null
@@ -0,0 +1,101 @@
+<?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 override deleted event class.
+ *
+ * @property-read array $other {
+ *      Extra information about event.
+ *
+ *      - int quizid: the id of the quiz.
+ * }
+ *
+ * @package    mod_quiz
+ * @since      Moodle 2.7
+ * @copyright  2014 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace mod_quiz\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+class user_override_deleted extends \core\event\base {
+
+    /**
+     * Init method.
+     */
+    protected function init() {
+        $this->data['objecttable'] = 'quiz_overrides';
+        $this->data['crud'] = 'd';
+        $this->data['edulevel'] = self::LEVEL_TEACHING;
+    }
+
+    /**
+     * Returns localised general event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('eventoverridedeleted', 'mod_quiz');
+    }
+
+    /**
+     * Returns description of what happened.
+     *
+     * @return string
+     */
+    public function get_description() {
+        return 'An override with the id ' . $this->objectid . ' for the quiz with the id of ' . $this->other['quizid'] .
+            ' was deleted by the user with the id ' . $this->userid . ' for the user with the id ' . $this->relateduserid;
+    }
+
+    /**
+     * Returns relevant URL.
+     *
+     * @return \moodle_url
+     */
+    public function get_url() {
+        return new \moodle_url('/mod/quiz/overrides.php', array('cmid' => $this->contextinstanceid));
+    }
+
+    /**
+     * Return the legacy event log data.
+     *
+     * @return array
+     */
+    protected function get_legacy_logdata() {
+        return array($this->courseid, 'quiz', 'delete override', 'overrides.php?cmid=' . $this->contextinstanceid,
+            $this->other['quizid'], $this->contextinstanceid);
+    }
+
+    /**
+     * Custom validation.
+     *
+     * @throws \coding_exception
+     * @return void
+     */
+    protected function validate_data() {
+        parent::validate_data();
+
+        if (!isset($this->relateduserid)) {
+            throw new \coding_exception('The \'relateduserid\' must be set.');
+        }
+
+        if (!isset($this->other['quizid'])) {
+            throw new \coding_exception('The \'quizid\' must be set in other.');
+        }
+    }
+}
diff --git a/mod/quiz/classes/event/user_override_updated.php b/mod/quiz/classes/event/user_override_updated.php
new file mode 100644 (file)
index 0000000..3c61d90
--- /dev/null
@@ -0,0 +1,101 @@
+<?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 override updated event class.
+ *
+ * @property-read array $other {
+ *      Extra information about event.
+ *
+ *      - int quizid: the id of the quiz.
+ * }
+ *
+ * @package    mod_quiz
+ * @since      Moodle 2.7
+ * @copyright  2014 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace mod_quiz\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+class user_override_updated extends \core\event\base {
+
+    /**
+     * Init method.
+     */
+    protected function init() {
+        $this->data['objecttable'] = 'quiz_overrides';
+        $this->data['crud'] = 'u';
+        $this->data['edulevel'] = self::LEVEL_TEACHING;
+    }
+
+    /**
+     * Returns localised general event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('eventoverrideupdated', 'mod_quiz');
+    }
+
+    /**
+     * Returns description of what happened.
+     *
+     * @return string
+     */
+    public function get_description() {
+        return 'An override with the id ' . $this->objectid . ' for the quiz with the id of ' . $this->other['quizid'] .
+            ' was updated by the user with the id ' . $this->userid . ' for the user with the id ' . $this->relateduserid;
+    }
+
+    /**
+     * Returns relevant URL.
+     *
+     * @return \moodle_url
+     */
+    public function get_url() {
+        return new \moodle_url('/mod/quiz/overrideedit.php', array('id' => $this->objectid));
+    }
+
+    /**
+     * Return the legacy event log data.
+     *
+     * @return array
+     */
+    protected function get_legacy_logdata() {
+        return array($this->courseid, 'quiz', 'edit override', 'overrideedit.php?id=' . $this->objectid, $this->other['quizid'],
+            $this->contextinstanceid);
+    }
+
+    /**
+     * Custom validation.
+     *
+     * @throws \coding_exception
+     * @return void
+     */
+    protected function validate_data() {
+        parent::validate_data();
+
+        if (!isset($this->relateduserid)) {
+            throw new \coding_exception('The \'relateduserid\' must be set.');
+        }
+
+        if (!isset($this->other['quizid'])) {
+            throw new \coding_exception('The \'quizid\' must be set in other.');
+        }
+    }
+}
index 754b88d..c1961ab 100644 (file)
@@ -42,11 +42,6 @@ if (!$attemptobj->is_finished()) {
 require_login($attemptobj->get_course(), false, $attemptobj->get_cm());
 $attemptobj->require_capability('mod/quiz:grade');
 
-// Log this action.
-add_to_log($attemptobj->get_courseid(), 'quiz', 'manualgrade', 'comment.php?attempt=' .
-        $attemptobj->get_attemptid() . '&slot=' . $slot,
-        $attemptobj->get_quizid(), $attemptobj->get_cmid());
-
 // Print the page header.
 $PAGE->set_pagelayout('popup');
 $PAGE->set_heading($attemptobj->get_course()->fullname);
@@ -74,6 +69,21 @@ if (data_submitted() && confirm_sesskey()) {
         $transaction = $DB->start_delegated_transaction();
         $attemptobj->process_submitted_actions(time());
         $transaction->allow_commit();
+
+        // Log this action.
+        $params = array(
+            'objectid' => $attemptobj->get_question_attempt($slot)->get_question()->id,
+            'courseid' => $attemptobj->get_courseid(),
+            'context' => context_module::instance($attemptobj->get_cmid()),
+            'other' => array(
+                'quizid' => $attemptobj->get_quizid(),
+                'attemptid' => $attemptobj->get_attemptid(),
+                'slot' => $slot
+            )
+        );
+        $event = \mod_quiz\event\question_manually_graded::create($params);
+        $event->trigger();
+
         echo $output->notification(get_string('changessaved'), 'notifysuccess');
         close_window(2, true);
         die;
index 82af005..9cbf9c9 100644 (file)
@@ -152,13 +152,20 @@ if (!$course) {
 $questionbank = new quiz_question_bank_view($contexts, $thispageurl, $course, $cm, $quiz);
 $questionbank->set_quiz_has_attempts($quizhasattempts);
 
-// Log this visit.
-add_to_log($cm->course, 'quiz', 'editquestions',
-            "view.php?id=$cm->id", "$quiz->id", $cm->id);
-
 // You need mod/quiz:manage in addition to question capabilities to access this page.
 require_capability('mod/quiz:manage', $contexts->lowest());
 
+// Log this visit.
+$params = array(
+    'courseid' => $course->id,
+    'context' => $contexts->lowest(),
+    'other' => array(
+        'quizid' => $quiz->id
+    )
+);
+$event = \mod_quiz\event\edit_page_viewed::create($params);
+$event->trigger();
+
 // Process commands ============================================================
 
 // Get the list of question ids had their check-boxes ticked.
index 5dd8d8e..f119782 100644 (file)
@@ -35,7 +35,11 @@ $coursecontext = context_course::instance($id);
 require_login($course);
 $PAGE->set_pagelayout('incourse');
 
-add_to_log($course->id, "quiz", "view all", "index.php?id=$course->id", "");
+$params = array(
+    'context' => $coursecontext
+);
+$event = \mod_quiz\event\course_module_instance_list_viewed::create($params);
+$event->trigger();
 
 // Print the header.
 $strquizzes = get_string("modulenameplural", "quiz");
index 4b2c936..18cc1e6 100644 (file)
@@ -305,10 +305,21 @@ $string['errornotnumbers'] = 'Error - answers must be numeric';
 $string['errorunexpectedevent'] = 'Unexpected event code {$a->event} found for question {$a->questionid} in attempt {$a->attemptid}.';
 $string['essay'] = 'Essay';
 $string['essayquestions'] = 'Questions';
+$string['eventattemptdeleted'] = 'Quiz attempt deleted';
+$string['eventattemptpreviewstarted'] = 'Quiz attempt preview started';
+$string['eventattemptreviewed'] = 'Quiz attempt reviewed';
+$string['eventattemptsummaryviewed'] = 'Quiz attempt summary viewed';
+$string['eventattemptviewed'] = 'Quiz attempt viewed';
+$string['eventeditpageviewed'] = 'Quiz edit page viewed';
+$string['eventoverridecreated'] = 'Quiz override created';
+$string['eventoverridedeleted'] = 'Quiz override deleted';
+$string['eventoverrideupdated'] = 'Quiz override updated';
+$string['eventquestionmanuallygraded'] = 'Question manually graded';
 $string['eventquizattemptabandoned'] = 'Quiz attempt abandoned';
 $string['eventquizattempttimelimitexceeded'] = 'Quiz attempt time limit exceeded';
 $string['eventquizattemptstarted'] = 'Quiz attempt started';
 $string['eventquizattemptsubmitted'] = 'Quiz attempt submitted';
+$string['eventreportviewed'] = 'Quiz report viewed';
 $string['everynquestions'] = 'Every {$a} questions';
 $string['everyquestion'] = 'Every question';
 $string['everythingon'] = 'Everything on';
index f6090c0..ffe7e54 100644 (file)
@@ -191,6 +191,11 @@ function quiz_delete_instance($id) {
 function quiz_delete_override($quiz, $overrideid) {
     global $DB;
 
+    if (!isset($quiz->cmid)) {
+        $cm = get_coursemodule_from_instance('quiz', $quiz->id, $quiz->course);
+        $quiz->cmid = $cm->id;
+    }
+
     $override = $DB->get_record('quiz_overrides', array('id' => $overrideid), '*', MUST_EXIST);
 
     // Delete the events.
@@ -203,6 +208,28 @@ function quiz_delete_override($quiz, $overrideid) {
     }
 
     $DB->delete_records('quiz_overrides', array('id' => $overrideid));
+
+    // Set the common parameters for one of the events we will be triggering.
+    $params = array(
+        'objectid' => $override->id,
+        'context' => context_module::instance($quiz->cmid),
+        'other' => array(
+            'quizid' => $override->quiz
+        )
+    );
+    // Determine which override deleted event to fire.
+    if (!empty($override->userid)) {
+        $params['relateduserid'] = $override->userid;
+        $event = \mod_quiz\event\user_override_deleted::create($params);
+    } else {
+        $params['other']['groupid'] = $override->groupid;
+        $event = \mod_quiz\event\group_override_deleted::create($params);
+    }
+
+    // Trigger the override deleted event.
+    $event->add_record_snapshot('quiz_overrides', $override);
+    $event->trigger();
+
     return true;
 }
 
index f24f3fc..47eb0bc 100644 (file)
@@ -289,14 +289,29 @@ function quiz_attempt_save_started($quizobj, $quba, $attempt) {
     question_engine::save_questions_usage_by_activity($quba);
     $attempt->uniqueid = $quba->get_id();
     $attempt->id = $DB->insert_record('quiz_attempts', $attempt);
-    // Log the new attempt.
+
+    // Params used by the events below.
+    $params = array(
+        'objectid' => $attempt->id,
+        'relateduserid' => $attempt->userid,
+        'courseid' => $quizobj->get_courseid(),
+        'context' => $quizobj->get_context()
+    );
+    // Decide which event we are using.
     if ($attempt->preview) {
-        add_to_log($quizobj->get_courseid(), 'quiz', 'preview', 'view.php?id='.$quizobj->get_cmid(),
-                   $quizobj->get_quizid(), $quizobj->get_cmid());
+        $params['other'] = array(
+            'quizid' => $quizobj->get_quizid()
+        );
+        $event = \mod_quiz\event\attempt_preview_started::create($params);
     } else {
-        add_to_log($quizobj->get_courseid(), 'quiz', 'attempt', 'review.php?attempt='.$attempt->id,
-                   $quizobj->get_quizid(), $quizobj->get_cmid());
+        $event = \mod_quiz\event\attempt_started::create($params);
+
     }
+
+    // Trigger the event.
+    $event->add_record_snapshot('quiz', $quizobj->get_quiz());
+    $event->trigger();
+
     return $attempt;
 }
 
@@ -360,6 +375,19 @@ function quiz_delete_attempt($attempt, $quiz) {
     question_engine::delete_questions_usage_by_activity($attempt->uniqueid);
     $DB->delete_records('quiz_attempts', array('id' => $attempt->id));
 
+    // Log the deletion of the attempt.
+    $params = array(
+        'objectid' => $attempt->id,
+        'relateduserid' => $attempt->userid,
+        'context' => context_module::instance($quiz->cmid),
+        'other' => array(
+            'quizid' => $quiz->id
+        )
+    );
+    $event = \mod_quiz\event\attempt_deleted::create($params);
+    $event->add_record_snapshot('quiz_attempts', $attempt);
+    $event->trigger();
+
     // Search quiz_attempts for other instances by this user.
     // If none, then delete record for this quiz, this user from quiz_grades
     // else recalculate best grade.
index 8d9144f..1f26b63 100644 (file)
@@ -61,11 +61,10 @@ if (!empty($override->userid)) {
 if ($confirm) {
     require_sesskey();
 
+    // Set the course module id before calling quiz_delete_override().
+    $quiz->cmid = $cm->id;
     quiz_delete_override($quiz, $override->id);
 
-    add_to_log($cm->course, 'quiz', 'delete override',
-        "overrides.php?cmid=$cm->id", $quiz->id, $cm->id);
-
     redirect($cancelurl);
 }
 
index 1648ab9..ec09cf3 100644 (file)
@@ -155,25 +155,56 @@ if ($mform->is_cancelled()) {
                     $fromform->{$key} = $oldoverride->{$key};
                 }
             }
-            // Delete the old override.
-            $DB->delete_records('quiz_overrides', array('id' => $oldoverride->id));
+            // Set the course module id before calling quiz_delete_override().
+            $quiz->cmid = $cm->id;
+            quiz_delete_override($quiz, $oldoverride->id);
         }
     }
 
+    // Set the common parameters for one of the events we may be triggering.
+    $params = array(
+        'context' => $context,
+        'other' => array(
+            'quizid' => $quiz->id
+        )
+    );
     if (!empty($override->id)) {
         $fromform->id = $override->id;
         $DB->update_record('quiz_overrides', $fromform);
+
+        // Determine which override updated event to fire.
+        $params['objectid'] = $override->id;
+        if (!$groupmode) {
+            $params['relateduserid'] = $fromform->userid;
+            $event = \mod_quiz\event\user_override_updated::create($params);
+        } else {
+            $params['other']['groupid'] = $fromform->groupid;
+            $event = \mod_quiz\event\group_override_updated::create($params);
+        }
+
+        // Trigger the override updated event.
+        $event->trigger();
     } else {
         unset($fromform->id);
         $fromform->id = $DB->insert_record('quiz_overrides', $fromform);
+
+        // Determine which override created event to fire.
+        $params['objectid'] = $fromform->id;
+        if (!$groupmode) {
+            $params['relateduserid'] = $fromform->userid;
+            $event = \mod_quiz\event\user_override_created::create($params);
+        } else {
+            $params['other']['groupid'] = $fromform->groupid;
+            $event = \mod_quiz\event\group_override_created::create($params);
+        }
+
+        // Trigger the override created event.
+        $event->trigger();
     }
 
     quiz_update_open_attempts(array('quizid'=>$quiz->id));
     quiz_update_events($quiz, $fromform);
 
-    add_to_log($cm->course, 'quiz', 'edit override',
-            "overrideedit.php?id=$fromform->id", $quiz->id, $cm->id);
-
     if (!empty($fromform->submitbutton)) {
         redirect($overridelisturl);
     }
index ccefbab..edf6d8c 100644 (file)
@@ -157,13 +157,6 @@ if (!$finishattempt) {
     }
 }
 
-// Otherwise, we have been asked to finish attempt, so do that.
-
-// Log the end of this attempt.
-add_to_log($attemptobj->get_courseid(), 'quiz', 'close attempt',
-        'review.php?attempt=' . $attemptobj->get_attemptid(),
-        $attemptobj->get_quizid(), $attemptobj->get_cmid());
-
 // Update the quiz attempt record.
 try {
     if ($becomingabandoned) {
index ddc8c52..2be5173 100644 (file)
@@ -82,9 +82,6 @@ if (!is_readable("report/$mode/report.php")) {
     print_error('reportnotfound', 'quiz', '', $mode);
 }
 
-add_to_log($course->id, 'quiz', 'report', 'report.php?id=' . $cm->id . '&mode=' . $mode,
-        $quiz->id, $cm->id);
-
 // Open the selected quiz report and display it.
 $file = $CFG->dirroot . '/mod/quiz/report/' . $mode . '/report.php';
 if (is_readable($file)) {
@@ -100,3 +97,16 @@ $report->display($quiz, $cm, $course);
 
 // Print footer.
 echo $OUTPUT->footer();
+
+// Log that this report was viewed.
+$params = array(
+    'context' => $context,
+    'other' => array(
+        'quizid' => $quiz->id,
+        'reportname' => $mode
+    )
+);
+$event = \mod_quiz\event\report_viewed::create($params);
+$event->add_record_snapshot('course', $course);
+$event->add_record_snapshot('quiz', $quiz);
+$event->trigger();
index 4ea24a9..2342d85 100644 (file)
@@ -317,8 +317,7 @@ abstract class quiz_attempts_report extends quiz_default_report {
                 // Ensure the attempt belongs to a student included in the report. If not skip.
                 continue;
             }
-            add_to_log($quiz->course, 'quiz', 'delete attempt', 'report.php?id=' . $cm->id,
-                    $attemptid, $cm->id);
+
             quiz_delete_attempt($attempt, $quiz);
         }
     }
diff --git a/mod/quiz/report/statistics/tests/fixtures/questions03.csv b/mod/quiz/report/statistics/tests/fixtures/questions03.csv
new file mode 100644 (file)
index 0000000..ba8b3e8
--- /dev/null
@@ -0,0 +1,2 @@
+slot,type,which,cat,mark
+1,calculatedsimple,sumwithvariants,maincat,1
index 63dd846..e6c19b9 100644 (file)
@@ -2,3 +2,4 @@ testnumber,preferredbehaviour
 00,deferredfeedback
 01,interactive
 02,interactive
+03,deferredfeedback
index 50ba9ca..60de822 100644 (file)
@@ -10,7 +10,7 @@ slot,randq,variant,subpart,modelresponse,actualresponse,totalcount
 2,,1,1,[NO MATCH],-0.2,1
 2,,1,1,[NO MATCH],-1,1
 2,,4,1,{a} + {b} (±0.01 Relative),19.4,2
-2,,4,1,"[NO RESPONSE]",,1
+2,,4,1,[NO MATCH],0,1
 2,,4,1,[NO MATCH],-0.4,1
 3,,1,1,frog: amphibian,amphibian,25
 3,,1,2,cat: mammal,mammal,24
diff --git a/mod/quiz/report/statistics/tests/fixtures/responsecounts03.csv b/mod/quiz/report/statistics/tests/fixtures/responsecounts03.csv
new file mode 100644 (file)
index 0000000..78ecaab
--- /dev/null
@@ -0,0 +1,2 @@
+slot,variant,modelresponse,actualresponse,totalcount
+1,4,"{a} + {b} (±0.01 Relative)",19.4,1
diff --git a/mod/quiz/report/statistics/tests/fixtures/steps03.csv b/mod/quiz/report/statistics/tests/fixtures/steps03.csv
new file mode 100644 (file)
index 0000000..8c395b6
--- /dev/null
@@ -0,0 +1,2 @@
+quizattempt,firstname,lastname,responses.1.answer,variants.1
+1,John,Jones,19.4,4
index 3af0bda..4c2deb7 100644 (file)
@@ -85,10 +85,6 @@ if ($options->flags == question_display_options::EDITABLE && optional_param('sav
     redirect($attemptobj->review_url(null, $page, $showall));
 }
 
-// Log this review.
-add_to_log($attemptobj->get_courseid(), 'quiz', 'review', 'review.php?attempt=' .
-        $attemptobj->get_attemptid(), $attemptobj->get_quizid(), $attemptobj->get_cmid());
-
 // Work out appropriate title and whether blocks should be shown.
 if ($attemptobj->is_preview_user() && $attemptobj->is_own_attempt()) {
     $strreviewtitle = get_string('reviewofpreview', 'quiz');
@@ -255,3 +251,17 @@ $regions = $PAGE->blocks->get_regions();
 $PAGE->blocks->add_fake_block($navbc, reset($regions));
 
 echo $output->review_page($attemptobj, $slots, $page, $showall, $lastpage, $options, $summarydata);
+
+// Trigger an event for this review.
+$params = array(
+    'objectid' => $attemptobj->get_attemptid(),
+    'relateduserid' => $attemptobj->get_userid(),
+    'courseid' => $attemptobj->get_courseid(),
+    'context' => context_module::instance($attemptobj->get_cmid()),
+    'other' => array(
+        'quizid' => $attemptobj->get_quizid()
+    )
+);
+$event = \mod_quiz\event\attempt_reviewed::create($params);
+$event->add_record_snapshot('quiz_attempts', $attemptobj->get_attempt());
+$event->trigger();
index 106890a..b1ac720 100644 (file)
@@ -76,11 +76,6 @@ if ($attemptobj->is_finished()) {
     redirect($attemptobj->review_url());
 }
 
-// Log this page view.
-add_to_log($attemptobj->get_courseid(), 'quiz', 'view summary',
-        'summary.php?attempt=' . $attemptobj->get_attemptid(),
-        $attemptobj->get_quizid(), $attemptobj->get_cmid());
-
 // Arrange for the navigation to be displayed.
 if (empty($attemptobj->get_quiz()->showblocks)) {
     $PAGE->blocks->show_only_fake_blocks();
@@ -96,3 +91,17 @@ $PAGE->set_heading($attemptobj->get_course()->fullname);
 
 // Display the page.
 echo $output->summary_page($attemptobj, $displayoptions);
+
+// Log this page view.
+$params = array(
+    'objectid' => $attemptobj->get_attemptid(),
+    'relateduserid' => $attemptobj->get_userid(),
+    'courseid' => $attemptobj->get_courseid(),
+    'context' => context_module::instance($attemptobj->get_cmid()),
+    'other' => array(
+        'quizid' => $attemptobj->get_quizid()
+    )
+);
+$event = \mod_quiz\event\attempt_summary_viewed::create($params);
+$event->add_record_snapshot('quiz_attempts', $attemptobj->get_attempt());
+$event->trigger();
index d7d34b3..bad32a0 100644 (file)
@@ -222,5 +222,498 @@ class mod_quiz_events_testcase extends advanced_testcase {
         $this->assertEquals('quiz_attempt_started', $event->get_legacy_eventname());
         $this->assertEventLegacyData($legacydata, $event);
         $this->assertEventContextNotUsed($event);
+
+        // Create another attempt.
+        $attempt = quiz_create_attempt($quizobj, 1, false, time(), false, 2);
+
+        // Trigger and capture the event.
+        $sink = $this->redirectEvents();
+        quiz_attempt_save_started($quizobj, $quba, $attempt);
+        $events = $sink->get_events();
+        $event = reset($events);
+
+        // Check that the event data is valid.
+        $this->assertInstanceOf('\mod_quiz\event\attempt_started', $event);
+        $this->assertEquals(context_module::instance($quizobj->get_cmid()), $event->get_context());
+        $expected = array($quizobj->get_courseid(), 'quiz', 'attempt', 'review.php?attempt=' . $attempt->id,
+            $quizobj->get_quizid(), $quizobj->get_cmid());
+        $this->assertEventLegacyLogData($expected, $event);
+    }
+
+    /**
+     * Test the edit page viewed event.
+     *
+     * There is no external API for updating a quiz, so the unit test will simply
+     * create and trigger the event and ensure the event data is returned as expected.
+     */
+    public function test_edit_page_viewed() {
+        $this->resetAfterTest();
+
+        $this->setAdminUser();
+        $course = $this->getDataGenerator()->create_course();
+        $quiz = $this->getDataGenerator()->create_module('quiz', array('course' => $course->id));
+
+        $params = array(
+            'courseid' => $course->id,
+            'context' => context_module::instance($quiz->cmid),
+            'other' => array(
+                'quizid' => $quiz->id
+            )
+        );
+        $event = \mod_quiz\event\edit_page_viewed::create($params);
+
+        // Trigger and capture the event.
+        $sink = $this->redirectEvents();
+        $event->trigger();
+        $events = $sink->get_events();
+        $event = reset($events);
+
+        // Check that the event data is valid.
+        $this->assertInstanceOf('\mod_quiz\event\edit_page_viewed', $event);
+        $this->assertEquals(context_module::instance($quiz->cmid), $event->get_context());
+        $expected = array($course->id, 'quiz', 'editquestions', 'view.php?id=' . $quiz->cmid, $quiz->id, $quiz->cmid);
+        $this->assertEventLegacyLogData($expected, $event);
+        $this->assertEventContextNotUsed($event);
+    }
+
+    /**
+     * Test the attempt deleted event.
+     */
+    public function test_attempt_deleted() {
+        list($quizobj, $quba, $attempt) = $this->prepare_quiz_data();
+
+        // Trigger and capture the event.
+        $sink = $this->redirectEvents();
+        quiz_delete_attempt($attempt, $quizobj->get_quiz());
+        $events = $sink->get_events();
+        $event = reset($events);
+
+        // Check that the event data is valid.
+        $this->assertInstanceOf('\mod_quiz\event\attempt_deleted', $event);
+        $this->assertEquals(context_module::instance($quizobj->get_cmid()), $event->get_context());
+        $expected = array($quizobj->get_courseid(), 'quiz', 'delete attempt', 'report.php?id=' . $quizobj->get_cmid(),
+            $attempt->id, $quizobj->get_cmid());
+        $this->assertEventLegacyLogData($expected, $event);
+        $this->assertEventContextNotUsed($event);
+    }
+
+    /**
+     * Test the report viewed event.
+     *
+     * There is no external API for viewing reports, so the unit test will simply
+     * create and trigger the event and ensure the event data is returned as expected.
+     */
+    public function test_report_viewed() {
+        $this->resetAfterTest();
+
+        $this->setAdminUser();
+        $course = $this->getDataGenerator()->create_course();
+        $quiz = $this->getDataGenerator()->create_module('quiz', array('course' => $course->id));
+
+        $params = array(
+            'context' => $context = context_module::instance($quiz->cmid),
+            'other' => array(
+                'quizid' => $quiz->id,
+                'reportname' => 'overview'
+            )
+        );
+        $event = \mod_quiz\event\report_viewed::create($params);
+
+        // Trigger and capture the event.
+        $sink = $this->redirectEvents();
+        $event->trigger();
+        $events = $sink->get_events();
+        $event = reset($events);
+
+        // Check that the event data is valid.
+        $this->assertInstanceOf('\mod_quiz\event\report_viewed', $event);
+        $this->assertEquals(context_module::instance($quiz->cmid), $event->get_context());
+        $expected = array($course->id, 'quiz', 'report', 'report.php?id=' . $quiz->cmid . '&mode=overview',
+            $quiz->id, $quiz->cmid);
+        $this->assertEventLegacyLogData($expected, $event);
+        $this->assertEventContextNotUsed($event);
+    }
+
+    /**
+     * Test the attempt reviewed event.
+     *
+     * There is no external API for reviewing attempts, so the unit test will simply
+     * create and trigger the event and ensure the event data is returned as expected.
+     */
+    public function test_attempt_reviewed() {
+        $this->resetAfterTest();
+
+        $this->setAdminUser();
+        $course = $this->getDataGenerator()->create_course();
+        $quiz = $this->getDataGenerator()->create_module('quiz', array('course' => $course->id));
+
+        $params = array(
+            'objectid' => 1,
+            'relateduserid' => 2,
+            'courseid' => $course->id,
+            'context' => context_module::instance($quiz->cmid),
+            'other' => array(
+                'quizid' => $quiz->id
+            )
+        );
+        $event = \mod_quiz\event\attempt_reviewed::create($params);
+
+        // Trigger and capture the event.
+        $sink = $this->redirectEvents();
+        $event->trigger();
+        $events = $sink->get_events();
+        $event = reset($events);
+
+        // Check that the event data is valid.
+        $this->assertInstanceOf('\mod_quiz\event\attempt_reviewed', $event);
+        $this->assertEquals(context_module::instance($quiz->cmid), $event->get_context());
+        $expected = array($course->id, 'quiz', 'review', 'review.php?attempt=1', $quiz->id, $quiz->cmid);
+        $this->assertEventLegacyLogData($expected, $event);
+        $this->assertEventContextNotUsed($event);
+    }
+
+    /**
+     * Test the attempt summary viewed event.
+     *
+     * There is no external API for viewing the attempt summary, so the unit test will simply
+     * create and trigger the event and ensure the event data is returned as expected.
+     */
+    public function test_attempt_summary_viewed() {
+        $this->resetAfterTest();
+
+        $this->setAdminUser();
+        $course = $this->getDataGenerator()->create_course();
+        $quiz = $this->getDataGenerator()->create_module('quiz', array('course' => $course->id));
+
+        $params = array(
+            'objectid' => 1,
+            'relateduserid' => 2,
+            'courseid' => $course->id,
+            'context' => context_module::instance($quiz->cmid),
+            'other' => array(
+                'quizid' => $quiz->id
+            )
+        );
+        $event = \mod_quiz\event\attempt_summary_viewed::create($params);
+
+        // Trigger and capture the event.
+        $sink = $this->redirectEvents();
+        $event->trigger();
+        $events = $sink->get_events();
+        $event = reset($events);
+
+        // Check that the event data is valid.
+        $this->assertInstanceOf('\mod_quiz\event\attempt_summary_viewed', $event);
+        $this->assertEquals(context_module::instance($quiz->cmid), $event->get_context());
+        $expected = array($course->id, 'quiz', 'view summary', 'summary.php?attempt=1', $quiz->id, $quiz->cmid);
+        $this->assertEventLegacyLogData($expected, $event);
+        $this->assertEventContextNotUsed($event);
+    }
+
+    /**
+     * Test the user override created event.
+     *
+     * There is no external API for creating a user override, so the unit test will simply
+     * create and trigger the event and ensure the event data is returned as expected.
+     */
+    public function test_user_override_created() {
+        $this->resetAfterTest();
+
+        $this->setAdminUser();
+        $course = $this->getDataGenerator()->create_course();
+        $quiz = $this->getDataGenerator()->create_module('quiz', array('course' => $course->id));
+
+        $params = array(
+            'objectid' => 1,
+            'relateduserid' => 2,
+            'context' => context_module::instance($quiz->cmid),
+            'other' => array(
+                'quizid' => $quiz->id
+            )
+        );
+        $event = \mod_quiz\event\user_override_created::create($params);
+
+        // Trigger and capture the event.
+        $sink = $this->redirectEvents();
+        $event->trigger();
+        $events = $sink->get_events();
+        $event = reset($events);
+
+        // Check that the event data is valid.
+        $this->assertInstanceOf('\mod_quiz\event\user_override_created', $event);
+        $this->assertEquals(context_module::instance($quiz->cmid), $event->get_context());
+        $this->assertEventContextNotUsed($event);
+    }
+
+    /**
+     * Test the group override created event.
+     *
+     * There is no external API for creating a group override, so the unit test will simply
+     * create and trigger the event and ensure the event data is returned as expected.
+     */
+    public function test_group_override_created() {
+        $this->resetAfterTest();
+
+        $this->setAdminUser();
+        $course = $this->getDataGenerator()->create_course();
+        $quiz = $this->getDataGenerator()->create_module('quiz', array('course' => $course->id));
+
+        $params = array(
+            'objectid' => 1,
+            'context' => context_module::instance($quiz->cmid),
+            'other' => array(
+                'quizid' => $quiz->id,
+                'groupid' => 2
+            )
+        );
+        $event = \mod_quiz\event\group_override_created::create($params);
+
+        // Trigger and capture the event.
+        $sink = $this->redirectEvents();
+        $event->trigger();
+        $events = $sink->get_events();
+        $event = reset($events);
+
+        // Check that the event data is valid.
+        $this->assertInstanceOf('\mod_quiz\event\group_override_created', $event);
+        $this->assertEquals(context_module::instance($quiz->cmid), $event->get_context());
+        $this->assertEventContextNotUsed($event);
+    }
+
+    /**
+     * Test the user override updated event.
+     *
+     * There is no external API for updating a user override, so the unit test will simply
+     * create and trigger the event and ensure the event data is returned as expected.
+     */
+    public function test_user_override_updated() {
+        $this->resetAfterTest();
+
+        $this->setAdminUser();
+        $course = $this->getDataGenerator()->create_course();
+        $quiz = $this->getDataGenerator()->create_module('quiz', array('course' => $course->id));
+
+        $params = array(
+            'objectid' => 1,
+            'relateduserid' => 2,
+            'context' => context_module::instance($quiz->cmid),
+            'other' => array(
+                'quizid' => $quiz->id
+            )
+        );
+        $event = \mod_quiz\event\user_override_updated::create($params);
+
+        // Trigger and capture the event.
+        $sink = $this->redirectEvents();
+        $event->trigger();
+        $events = $sink->get_events();
+        $event = reset($events);
+
+        // Check that the event data is valid.
+        $this->assertInstanceOf('\mod_quiz\event\user_override_updated', $event);
+        $this->assertEquals(context_module::instance($quiz->cmid), $event->get_context());
+        $expected = array($course->id, 'quiz', 'edit override', 'overrideedit.php?id=1', $quiz->id, $quiz->cmid);
+        $this->assertEventLegacyLogData($expected, $event);
+        $this->assertEventContextNotUsed($event);
+    }
+
+    /**
+     * Test the group override updated event.
+     *
+     * There is no external API for updating a group override, so the unit test will simply
+     * create and trigger the event and ensure the event data is returned as expected.
+     */
+    public function test_group_override_updated() {
+        $this->resetAfterTest();
+
+        $this->setAdminUser();
+        $course = $this->getDataGenerator()->create_course();
+        $quiz = $this->getDataGenerator()->create_module('quiz', array('course' => $course->id));
+
+        $params = array(
+            'objectid' => 1,
+            'context' => context_module::instance($quiz->cmid),
+            'other' => array(
+                'quizid' => $quiz->id,
+                'groupid' => 2
+            )
+        );
+        $event = \mod_quiz\event\group_override_updated::create($params);
+
+        // Trigger and capture the event.
+        $sink = $this->redirectEvents();
+        $event->trigger();
+        $events = $sink->get_events();
+        $event = reset($events);
+
+        // Check that the event data is valid.
+        $this->assertInstanceOf('\mod_quiz\event\group_override_updated', $event);
+        $this->assertEquals(context_module::instance($quiz->cmid), $event->get_context());
+        $expected = array($course->id, 'quiz', 'edit override', 'overrideedit.php?id=1', $quiz->id, $quiz->cmid);
+        $this->assertEventLegacyLogData($expected, $event);
+        $this->assertEventContextNotUsed($event);
+    }
+
+    /**
+     * Test the user override deleted event.
+     */
+    public function test_user_override_deleted() {
+        global $DB;
+
+        $this->resetAfterTest();
+
+        $this->setAdminUser();
+        $course = $this->getDataGenerator()->create_course();
+        $quiz = $this->getDataGenerator()->create_module('quiz', array('course' => $course->id));
+
+        // Create an override.
+        $override = new stdClass();
+        $override->quiz = $quiz->id;
+        $override->userid = 2;
+        $override->id = $DB->insert_record('quiz_overrides', $override);
+
+        // Trigger and capture the event.
+        $sink = $this->redirectEvents();
+        quiz_delete_override($quiz, $override->id);
+        $events = $sink->get_events();
+        $event = reset($events);
+
+        // Check that the event data is valid.
+        $this->assertInstanceOf('\mod_quiz\event\user_override_deleted', $event);
+        $this->assertEquals(context_module::instance($quiz->cmid), $event->get_context());
+        $expected = array($course->id, 'quiz', 'delete override', 'overrides.php?cmid=' . $quiz->cmid, $quiz->id, $quiz->cmid);
+        $this->assertEventLegacyLogData($expected, $event);
+        $this->assertEventContextNotUsed($event);
+    }
+
+    /**
+     * Test the group override deleted event.
+     */
+    public function test_group_override_deleted() {
+        global $DB;
+
+        $this->resetAfterTest();
+
+        $this->setAdminUser();
+        $course = $this->getDataGenerator()->create_course();
+        $quiz = $this->getDataGenerator()->create_module('quiz', array('course' => $course->id));
+
+        // Create an override.
+        $override = new stdClass();
+        $override->quiz = $quiz->id;
+        $override->groupid = 2;
+        $override->id = $DB->insert_record('quiz_overrides', $override);
+
+        // Trigger and capture the event.
+        $sink = $this->redirectEvents();
+        quiz_delete_override($quiz, $override->id);
+        $events = $sink->get_events();
+        $event = reset($events);
+
+        // Check that the event data is valid.
+        $this->assertInstanceOf('\mod_quiz\event\group_override_deleted', $event);
+        $this->assertEquals(context_module::instance($quiz->cmid), $event->get_context());
+        $expected = array($course->id, 'quiz', 'delete override', 'overrides.php?cmid=' . $quiz->cmid, $quiz->id, $quiz->cmid);
+        $this->assertEventLegacyLogData($expected, $event);
+        $this->assertEventContextNotUsed($event);
+    }
+
+    /**
+     * Test the attempt viewed event.
+     *
+     * There is no external API for continuing an attempt, so the unit test will simply
+     * create and trigger the event and ensure the event data is returned as expected.
+     */
+    public function test_attempt_viewed() {
+        $this->resetAfterTest();
+
+        $this->setAdminUser();
+        $course = $this->getDataGenerator()->create_course();
+        $quiz = $this->getDataGenerator()->create_module('quiz', array('course' => $course->id));
+
+        $params = array(
+            'objectid' => 1,
+            'relateduserid' => 2,
+            'courseid' => $course->id,
+            'context' => context_module::instance($quiz->cmid),
+            'other' => array(
+                'quizid' => $quiz->id
+            )
+        );
+        $event = \mod_quiz\event\attempt_viewed::create($params);
+
+        // Trigger and capture the event.
+        $sink = $this->redirectEvents();
+        $event->trigger();
+        $events = $sink->get_events();
+        $event = reset($events);
+
+        // Check that the event data is valid.
+        $this->assertInstanceOf('\mod_quiz\event\attempt_viewed', $event);
+        $this->assertEquals(context_module::instance($quiz->cmid), $event->get_context());
+        $expected = array($course->id, 'quiz', 'continue attempt', 'review.php?attempt=1', $quiz->id, $quiz->cmid);
+        $this->assertEventLegacyLogData($expected, $event);
+        $this->assertEventContextNotUsed($event);
+    }
+
+    /**
+     * Test the attempt previewed event.
+     */
+    public function test_attempt_preview_started() {
+        list($quizobj, $quba, $attempt) = $this->prepare_quiz_data();
+
+        // We want to preview this attempt.
+        $attempt = quiz_create_attempt($quizobj, 1, false, time(), false, 2);
+        $attempt->preview = 1;
+
+        // Trigger and capture the event.
+        $sink = $this->redirectEvents();
+        quiz_attempt_save_started($quizobj, $quba, $attempt);
+        $events = $sink->get_events();
+        $event = reset($events);
+
+        // Check that the event data is valid.
+        $this->assertInstanceOf('\mod_quiz\event\attempt_preview_started', $event);
+        $this->assertEquals(context_module::instance($quizobj->get_cmid()), $event->get_context());
+        $expected = array($quizobj->get_courseid(), 'quiz', 'preview', 'view.php?id=' . $quizobj->get_cmid(),
+            $quizobj->get_quizid(), $quizobj->get_cmid());
+        $this->assertEventLegacyLogData($expected, $event);
+        $this->assertEventContextNotUsed($event);
+    }
+
+    /**
+     * Test the question manually graded event.
+     *
+     * There is no external API for manually grading a question, so the unit test will simply
+     * create and trigger the event and ensure the event data is returned as expected.
+     */
+    public function test_question_manually_graded() {
+        list($quizobj, $quba, $attempt) = $this->prepare_quiz_data();
+
+        $params = array(
+            'objectid' => 1,
+            'courseid' => $quizobj->get_courseid(),
+            'context' => context_module::instance($quizobj->get_cmid()),
+            'other' => array(
+                'quizid' => $quizobj->get_quizid(),
+                'attemptid' => 2,
+                'slot' => 3
+            )
+        );
+        $event = \mod_quiz\event\question_manually_graded::create($params);
+
+        // Trigger and capture the event.
+        $sink = $this->redirectEvents();
+        $event->trigger();
+        $events = $sink->get_events();
+        $event = reset($events);
+
+        // Check that the event data is valid.
+        $this->assertInstanceOf('\mod_quiz\event\question_manually_graded', $event);
+        $this->assertEquals(context_module::instance($quizobj->get_cmid()), $event->get_context());
+        $expected = array($quizobj->get_courseid(), 'quiz', 'manualgrade', 'comment.php?attempt=2&slot=3',
+            $quizobj->get_quizid(), $quizobj->get_cmid());
+        $this->assertEventLegacyLogData($expected, $event);
+        $this->assertEventContextNotUsed($event);
     }
 }
index fbdb24a..a99f825 100644 (file)
@@ -70,7 +70,13 @@ $accessmanager = new quiz_access_manager($quizobj, $timenow,
 $quiz = $quizobj->get_quiz();
 
 // Log this request.
-add_to_log($course->id, 'quiz', 'view', 'view.php?id=' . $cm->id, $quiz->id, $cm->id);
+$params = array(
+    'objectid' => $quiz->id,
+    'context' => $context
+);
+$event = \mod_quiz\event\course_module_viewed::create($params);
+$event->add_record_snapshot('quiz', $quiz);
+$event->trigger();
 
 $completion = new completion_info($course);
 $completion->set_module_viewed($cm);
index b46d340..52fe90a 100644 (file)
@@ -4,6 +4,8 @@ information provided here is intended especially for developers.
 === 2.7 ===
 
 * modgrade form element has been redesigned and allows setting the maximum grade point higher than 100.
+* The usage of $module in mod/xxx/version.php files is now deprecated. Please use
+  $plugin instead. The support for the legacy notation will be dropped in Moodle 2.10.
 
 === 2.6 ===
 
index 39740fd..cd331b8 100644 (file)
@@ -404,6 +404,15 @@ class question_category_object {
         $cat->sortorder = 999;
         $cat->stamp = make_unique_id_code();
         $categoryid = $DB->insert_record("question_categories", $cat);
+
+        // Log the creation of this category.
+        $params = array(
+            'objectid' => $categoryid,
+            'contextid' => $contextid
+        );
+        $event = \core\event\question_category_created::create($params);
+        $event->trigger();
+
         if ($return) {
             return $categoryid;
         } else {
index 9f81568..1b53a05 100644 (file)
@@ -136,20 +136,6 @@ class all_calculated_for_qubaid_condition {
         return array_keys($this->questionstats);
     }
 
-    /**
-     * Array of variants of one randomly selected question that have appeared in the attempt data.
-     *
-     * @param int $questionid The id for the sub question.
-     * @return int[] The variant nos.
-     */
-    public function get_variants_for_subq($questionid) {
-        if (count($this->subquestionstats[$questionid]->variantstats) > 1) {
-            return array_keys($this->subquestionstats[$questionid]->variantstats);
-        } else {
-            return false;
-        }
-    }
-
     /**
      * Get position stats instance for a slot and optional variant no.
      *
@@ -351,7 +337,7 @@ class all_calculated_for_qubaid_condition {
         $toreturn = array();
         $displayorder = 1;
         foreach ($this->for_slot($slot)->get_sub_question_ids() as $subqid) {
-            if ($variants = $this->get_variants_for_subq($subqid)) {
+            if ($variants = $this->for_subq($subqid)->get_variants()) {
                 foreach ($variants as $variant) {
                     $toreturn[] = $this->make_new_subq_stat_for($displayorder, $slot, $subqid, $variant);
                 }
@@ -406,7 +392,7 @@ class all_calculated_for_qubaid_condition {
             $displaynumber = 1;
             foreach ($this->for_slot($slot)->get_sub_question_ids() as $subqid) {
                 $toreturn[] = $this->make_new_subq_stat_for($displaynumber, $slot, $subqid);
-                if ($variants = $this->get_variants_for_subq($subqid)) {
+                if ($variants = $this->for_subq($subqid)->get_variants()) {
                     foreach ($variants as $variant) {
                         $toreturn[] = $this->make_new_subq_stat_for($displaynumber, $slot, $subqid, $variant);
                     }
index 7d1fffe..e896ca9 100644 (file)
@@ -230,7 +230,7 @@ class calculated {
         }
         $DB->insert_record('question_statistics', $toinsert, false);
 
-        if (count($this->variantstats) > 1) {
+        if ($this->get_variants()) {
             foreach ($this->variantstats as $variantstat) {
                 $variantstat->cache($qubaids);
             }
@@ -276,7 +276,7 @@ class calculated {
      */
     public function get_variants() {
         $variants = array_keys($this->variantstats);
-        if (count($variants) > 1) {
+        if (count($variants) > 1 || reset($variants) != 1) {
             return $variants;
         } else {
             return array();
index 19370eb..85c471d 100644 (file)
@@ -161,7 +161,7 @@ class calculator {
                 $this->stats->for_subq($qid)->question = $subquestion;
                 $this->stats->for_subq($qid)->randomguessscore = $this->get_random_guess_score($subquestion);
 
-                if ($variants = $this->stats->get_variants_for_subq($qid)) {
+                if ($variants = $this->stats->for_subq($qid)->get_variants()) {
                     foreach ($variants as $variant) {
                         $this->stats->for_subq($qid, $variant)->question = $subquestion;
                         $this->stats->for_subq($qid, $variant)->randomguessscore = $this->get_random_guess_score($subquestion);
index 42c4aff..872f020 100644 (file)
@@ -145,7 +145,7 @@ class analysis_for_class {
             return true;
         } else if (count($actualresponses) === 1) {
             $singleactualresponse = reset($actualresponses);
-            return $singleactualresponse != $this->modelresponse;
+            return (string)$singleactualresponse != $this->modelresponse;
         }
         return false;
     }
diff --git a/question/tests/events_test.php b/question/tests/events_test.php
new file mode 100644 (file)
index 0000000..357422e
--- /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/>.
+
+/**
+ * Events tests.
+ *
+ * @package core_question
+ * @copyright 2014 Mark Nelson <markn@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+
+require_once($CFG->dirroot . '/question/editlib.php');
+require_once($CFG->dirroot . '/question/category_class.php');
+
+class core_question_events_testcase extends advanced_testcase {
+
+    /**
+     * Tests set up.
+     */
+    public function setUp() {
+        $this->resetAfterTest();
+    }
+
+    /**
+     * Test the question category created event.
+     */
+    public function test_question_category_created() {
+        $this->setAdminUser();
+        $course = $this->getDataGenerator()->create_course();
+        $quiz = $this->getDataGenerator()->create_module('quiz', array('course' => $course->id));
+
+        $contexts = new question_edit_contexts(context_module::instance($quiz->cmid));
+
+        $defaultcategoryobj = question_make_default_categories(array($contexts->lowest()));
+        $defaultcategory = $defaultcategoryobj->id . ',' . $defaultcategoryobj->contextid;
+
+        $qcobject = new question_category_object(
+            1,
+            new moodle_url('/mod/quiz/edit.php', array('cmid' => $quiz->cmid)),
+            $contexts->having_one_edit_tab_cap('categories'),
+            $defaultcategoryobj->id,
+            $defaultcategory,
+            null,
+            $contexts->having_cap('moodle/question:add'));
+
+        // Trigger and capture the event.
+        $sink = $this->redirectEvents();
+        $categoryid = $qcobject->add_category($defaultcategory, 'newcategory', '', true);
+        $events = $sink->get_events();
+        $event = reset($events);
+
+        // Check that the event data is valid.
+        $this->assertInstanceOf('\core\event\question_category_created', $event);
+        $this->assertEquals(context_module::instance($quiz->cmid), $event->get_context());
+        $expected = array($course->id, 'quiz', 'addcategory', 'view.php?id=' . $quiz->cmid , $categoryid, $quiz->cmid);
+        $this->assertEventLegacyLogData($expected, $event);
+        $this->assertEventContextNotUsed($event);
+    }
+}
index 8df2ce9..c86f2ff 100644 (file)
@@ -249,7 +249,7 @@ class qtype_numerical_question extends question_graded_automatically {
     }
 
     public function classify_response(array $response) {
-        if (empty($response['answer'])) {
+        if (!$this->is_gradable_response($response)) {
             return array($this->id => question_classified_response::no_response());
         }
 
index 490c6d5..f48ac17 100644 (file)
@@ -37,24 +37,6 @@ require_once(dirname(__FILE__) . '/wikimedia.php');
  */
 
 class repository_wikimedia extends repository {
-    public function __construct($repositoryid, $context = SYSCONTEXTID, $options = array()) {
-        global $SESSION;
-        parent::__construct($repositoryid, $context, $options);
-        $this->keyword = optional_param('wikimedia_keyword', '', PARAM_RAW);
-        if (empty($this->keyword)) {
-            $this->keyword = optional_param('s', '', PARAM_RAW);
-        }
-        $sess_keyword = 'wikimedia_'.$this->id.'_keyword';
-        if (empty($this->keyword) && optional_param('page', '', PARAM_RAW)) {
-            // This is the request of another page for the last search, retrieve the cached keyword
-            if (isset($SESSION->{$sess_keyword})) {
-                $this->keyword = $SESSION->{$sess_keyword};
-            }
-        } else if (!empty($this->keyword)) {
-            // save the search keyword in the session so we can retrieve it later
-            $SESSION->{$sess_keyword} = $this->keyword;
-        }
-    }
 
     /**
      * Returns maximum width for images
@@ -116,6 +98,21 @@ class repository_wikimedia extends repository {
     }
    // login
     public function check_login() {
+        global $SESSION;
+        $this->keyword = optional_param('wikimedia_keyword', '', PARAM_RAW);
+        if (empty($this->keyword)) {
+            $this->keyword = optional_param('s', '', PARAM_RAW);
+        }
+        $sess_keyword = 'wikimedia_'.$this->id.'_keyword';
+        if (empty($this->keyword) && optional_param('page', '', PARAM_RAW)) {
+            // This is the request of another page for the last search, retrieve the cached keyword.
+            if (isset($SESSION->{$sess_keyword})) {
+                $this->keyword = $SESSION->{$sess_keyword};
+            }
+        } else if (!empty($this->keyword)) {
+            // Save the search keyword in the session so we can retrieve it later.
+            $SESSION->{$sess_keyword} = $this->keyword;
+        }
         return !empty($this->keyword);
     }
     // if check_login returns false,
index 61929eb..8f26369 100644 (file)
@@ -231,7 +231,7 @@ $query = "
      LEFT JOIN {user} u ON u.id = tg.userid
                $where
       GROUP BY tg.id, tg.name, tg.rawname, tg.tagtype, tg.flag, tg.timemodified,
-               u.id, u.firstname, u.lastname
+               u.id, $allusernames
          $sort";
 
 $totalcount = $DB->count_records_sql("
index c6cea2f..0387661 100644 (file)
@@ -2229,3 +2229,11 @@ body.lockscroll {
   height: 100%;
   overflow: hidden;
 }
+
+.dir-rtl {
+    // Bootstrap sets right margin to 0. Fail.
+    // They set left margin to 25px so we will copy that.
+    ul, ol {
+        margin-right: 25px;
+    }
+}
index 436455c..2ae5e8a 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,.jsenabled.docked-region-side-post #block-region-side-post,.jsenabled.docked-region-side-pre #block-region-side-pre{display:none}.content-only #region-main.span9,.empty-region-side-post #region-bs-main-and-pre.span9,.empty-region-side-pre #region-bs-main-and-post.span9,.empty-region-side-post #region-bs-main-and-post.span9 #region-main.span8,.jsenabled.docked-region-side-post #region-bs-main-and-pre.span9,.jsenabled.docked-region-side-post #region-bs-main-and-post.span9 #region-main.span8,.jsenabled.docked-region-side-pre #region-bs-main-and-post.span9{width:100%}.empty-region-side-pre #region-bs-main-and-pre.span9 #region-main,.jsenabled.docked-region-side-pre #region-bs-main-and-pre.span9 #region-main{float:none;width:100%}.empty-region-side-post.used-region-side-pre #region-main.span8,.jsenabled.docked-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,.jsenabled.docked-region-side-post.used-region-side-pre #block-region-side-pre.span4{width:23.404255319148934%;*width:23.351063829787233%}.empty-region-side-pre #region-bs-main-and-post.span9 #region-main.span8,.jsenabled.docked-region-side-pre #region-bs-main-and-post.span9 #region-main.span8{float:right}.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}.highlight{background:#d9edf7}.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}.collapsible-actions{display:none;text-align:right}.dir-rtl .collapsible-actions{text-align:left}.jsenabled .collapsible-actions{display:block}.collapsible-actions .collapseexpand{padding-left:20px;background:url([[pix:t/collapsed]]) 2px center no-repeat}.dir-rtl .collapsible-actions .collapseexpand{padding-right:20px;padding-left:0;background:url([[pix:t/collapsed_rtl]]) right center no-repeat}.collapsible-actions .collapse-all,.dir-rtl .collapsible-actions .collapse-all{background-image:url([[pix:t/expanded]])}.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{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}h1 img.iconhelp,h1 img.icon,h2 img.iconhelp,h2 img.icon,h3 img.iconhelp,h3 img.icon,h4 img.iconhelp,h4 img.icon,h5 img.iconhelp,h5 img.icon,h6 img.iconhelp,h6 img.icon{padding:4px;vertical-align:middle}.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}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}.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 #buttonscell p.arrow_button input{width:auto;min-width:80%;margin:0 auto}.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{display:inline-block;margin-top:3px;margin-bottom:3px}.groupselector label{display:inline-block}.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}.dir-rtl .loginbox.twocolumns .loginpanel,.dir-rtl .loginbox.twocolumns .signuppanel{float:right}.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-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 rgba(0,0,0,0.2)}#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{color:#fff;background:#0070a8}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 tr{vertical-align:top}.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,.userenrolment .col_group .groups{margin-right:30px}.userenrolment .col_role .role,.userenrolment .col_group .group{float:left;padding:3px;margin:3px;white-space:nowrap}.userenrolment .col_role .role a,.userenrolment .col_group .group a{margin-left:3px;cursor:pointer}.userenrolment .col_role .addrole,.userenrolment .col_group .addgroup{float:right;padding:3px;margin:3px}.userenrolment .col_role .addrole>*:hover,.userenrolment .col_group .addgroup>*:hover{border-bottom:1px solid #666}.userenrolment .col_role .addrole img,.userenrolment .col_group .addgroup img{vertical-align:baseline}.dir-rtl .userenrolment .col_role .role{float:right}.userenrolment .hasAllRoles .col_role .addrole{display:none}.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}#page-enrol-users .enrol_user_buttons .enrolusersbutton div,#page-enrol-users .enrol_user_buttons .enrolusersbutton form{display:inline;margin-right:0}#page-enrol-users #filterform{display:inline-block;min-height:20px;padding:19px;padding:9px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-color:#e3e3e3;-webkit-border-radius:4px;-webkit-border-radius:3px;-moz-border-radius:4px;-moz-border-radius:3px;border-radius:4px;border-radius:3px;-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)}#page-enrol-users #filterform blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}#page-enrol-users #filterform .fitem{display:inline-block;margin-right:.3em;line-height:40px;white-space:nowrap}#page-enrol-users #filterform .fitem label{display:inline;padding-right:.3em;line-height:20px}#page-enrol-users #filterform .fitem :before,#page-enrol-users #filterform .fitem:after{display:inline}#page-enrol-users #filterform div,#page-enrol-users #filterform fieldset{display:inline;float:none;width:auto;margin:0;clear:none}#page-enrol-users #filterform select,#page-enrol-users #filterform .ftext input{width:7em}#page-enrol-users #filterform input,#page-enrol-users #filterform select{margin-bottom:0}#page-enrol-users .user-enroller-panel .uep-search-results .user .details{width:237px}.dir-rtl#page-enrol-users .col_userdetails .subfield_picture{float:right}.dir-rtl#page-enrol-users .enrol_user_buttons{float:left}.dir-rtl#page-enrol-users .enrol_user_buttons .enrolusersbutton{margin-right:1em;margin-left:0}.dir-rtl#page-enrol-users .enrol_user_buttons .enrolusersbutton div{margin-left:0}.dir-rtl#page-enrol-users #filterform .fitem{margin-right:0;margin-left:.3em}.dir-rtl#page-enrol-users #filterform .fitem label{padding-right:0;padding-left:.3em}.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-outer{display:table}.mod-indent{display:table-cell}.label .mod-indent{float:left;padding-top:20px}.mod-indent-1{width:30px}.mod-indent-2{width:60px}.mod-indent-3{width:90px}.mod-indent-4{width:120px}.mod-indent-5{width:150px}.mod-indent-6{width:180px}.mod-indent-7{width:210px}.mod-indent-8{width:240px}.mod-indent-9{width:270px}.mod-indent-10{width:300px}.mod-indent-11{width:330px}.mod-indent-12{width:360px}.mod-indent-13{width:390px}.mod-indent-14{width:420px}.mod-indent-15,.mod-indent-huge{width:420px}.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}body#page-lib-editor-tinymce-plugins-moodlemedia-preview{min-width:0;padding:0;margin:0;background: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,.initialbar strong{padding-right:3px;padding-left:3px}.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-base .moodle-dialogue-fullscreen{position:fixed;top:0;right:0;bottom:-50px;left:0}.moodle-dialogue-base .moodle-dialogue-fullscreen .moodle-dialogue-content{overflow:auto}.moodle-dialogue-base .moodle-dialogue-fullscreen .closebutton{width:28px;height:16px;background-size:100%}.moodle-dialogue-base .moodle-dialogue{z-index:600;padding:0;margin:0;background:0;border:0;outline:#000 dotted 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,.moodle-dialogue-base .moodle-dialogue-wrap .moodle-dialogue-hd.yui3-widget-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:0}.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;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-content{position:absolute;top:0;right:0;bottom:50px;left:0;margin:0;overflow:auto;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}.moodle-dialogue-base .moodle-dialogue.moodle-dialogue-tooltip .moodle-dialogue-bd{overflow:auto}#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{padding:.7em 0;text-align:center}@media(max-height:639px){.ios.safari .choosercontainer #chooseform .submitbuttons{padding:45px 0}}.choosercontainer #chooseform .submitbuttons input{min-width:100px;margin:0 .5em}.choosercontainer #chooseform .options{position:relative;border-bottom:1px solid #bbb}.jschooser .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.jschooser .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}.jschooser .choosercontainer #chooseform .instruction,.jschooser .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.jschooser .choosercontainer #chooseform .instruction,.dir-rtl.jschooser .choosercontainer #chooseform .typesummary{right:18.5em;left:0;border-right:1px solid grey}.jschooser .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:#f5f5f5}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}.dir-rtl table.badgeissuedinfo .bvalue,.dir-rtl table.badgeissuedinfo .bfield{text-align:right}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}.connecting{color:#c09853}#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}.dir-rtl .activatebadge{text-align:right}img#persona_signin{cursor:pointer}.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 .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-enhance] .toggle-display.textmenu{display:block;padding-right:4px;padding-left:4px;margin-left:4px}.jsenabled .moodle-actionmenu[data-enhance] .toggle-display.textmenu .iconsmall{padding:8px 4px 0 2px;margin:4px 4px 4px 0;vertical-align:text-bottom}.jsenabled .moodle-actionmenu[data-enhance] .toggle-display.textmenu .caret{margin-top:8px;margin-left:2px;border-top-color:#777}.jsenabled .moodle-actionmenu[data-enhance] .toggle-display.textmenu .caret:hover,.jsenabled .moodle-actionmenu[data-enhance] .toggle-display.textmenu .caret:active{border-top-color:#555}.jsenabled .moodle-actionmenu[data-enhanced] .toggle-display{opacity:1;filter:alpha(opacity=100)}.jsenabled .moodle-actionmenu[data-enhanced] .menu-action-text{display:inline}.jsenabled.dir-rtl .moodle-actionmenu[data-enhance] .toggle-display.textmenu{margin-right:4px;margin-left:initial}.jsenabled.dir-rtl .moodle-actionmenu[data-enhance] .toggle-display.textmenu .caret{margin-right:2px;margin-left:initial}.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 rgba(0,0,0,0.2);-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 28px;color:#333}.moodle-actionmenu[data-enhanced].show .menu a:hover{color:#fff;background-color:#0070a8}.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:4px 4px 4px -24px}.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}.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}ul.dragdrop-keyboard-drag li{list-style-type:none}.block-control-actions .moodle-core-dragdrop-draghandle img{width:12px;height:12px}a.disabled:hover,a.disabled{font-style:italic;color:#808080;text-decoration:none;cursor:default}body.lockscroll{height:100%;overflow:hidden}.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,#page-admin-report-backups-index .backup-notyetrun{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 .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:#f5f5f5;border:1px solid #ddd}#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 .adminerror{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 .adminerror{color:#b94a48;background-color:#f2dede;border-color:#eed3d7}#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-emoticons td input{width:8em}#admin-emoticons td.c0 input{width:4em}#adminthemeselector .selectedtheme td.c0{border:1px solid #000;border-right-width:0}#adminthemeselector .selectedtheme td.c1{border:1px solid #000;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 .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}#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}.maintenancewarning{position:fixed;right:0;bottom:0;z-index:1;padding:3px 1em;overflow:hidden;text-align:center}.maintenancewarning.error{font-weight:bold;color:#b94a48;background-color:#f2dede;border:2px solid #eed3d7}.maintenancewarning.warning{color:#c09853;background-color:#fcf8e3;border:2px solid #fbeed5}.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 .calendartable{width:100%}.path-calendar .calendartable th,.path-calendar .calendartable td{width:14%;text-align:center;vertical-align:top;border:0}.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 .filters table{width:100%;border-collapse:separate;border-spacing:2px}.path-calendar .cal_courses_flt label{margin-right:.45em}.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 .calendar_event_course,.path-calendar .maincalendar .calendar_event_global,.path-calendar .maincalendar .calendar_event_group,.path-calendar .maincalendar .calendar_event_user{border-style:solid;border-width:1px 1px 1px 12px}.path-calendar .maincalendar .calendar_event_course{border-color:#ffd3bd}.path-calendar .maincalendar .calendar_event_global{border-color:#d6f8cd}.path-calendar .maincalendar .calendar_event_group{border-color:#fee7ae}.path-calendar .maincalendar .calendar_event_user{border-color:#dce7ec}.path-calendar .maincalendar .calendar-event-panel{background-color:#eee;border:2px solid #eee}.path-calendar .maincalendar .calendar-event-panel .yui3-overlay-content{padding:19px;background-color:#fdfdfd;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)}.path-calendar .maincalendar .calendar-controls .current{font-family:inherit;font-size:25px;font-weight:bold;line-height:1.2;color:inherit}.path-calendar .maincalendar .calendartable td,.path-calendar .maincalendar .calendartable li{padding:5px}.path-calendar .maincalendar .calendartable li{padding-left:10px;text-align:left}.path-calendar .maincalendar .header{overflow:hidden}.path-calendar .maincalendar .header .buttons{float:right}.path-calendar .maincalendar .eventlist .event{position:relative;width:100%;padding:19px;margin-bottom:20px;background-color:#fdfdfd;border:1px solid #e3e3e3;border-collapse:separate;border-spacing:0;-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)}.path-calendar .maincalendar .eventlist .event .picture{vertical-align:text-top}.path-calendar .maincalendar .eventlist .event .topic .name{float:left;font-size:17.5px;font-weight:200;line-height:24px}.path-calendar .maincalendar .eventlist .event .topic .name,.path-calendar .maincalendar .eventlist .event .topic .course{margin-bottom:5px}.path-calendar .maincalendar .eventlist .event .topic .date{float:right}.path-calendar .maincalendar .eventlist .event .course,.path-calendar .maincalendar .eventlist .event .subscription{float:left;clear:left}.path-calendar .maincalendar .eventlist .event .side{width:22px}.path-calendar .maincalendar .eventlist .event .description{padding:5px;background-color:#fff}.path-calendar .maincalendar .eventlist .event .description .commands{position:absolute;top:0;right:0;margin:3px}.path-calendar .maincalendar .eventlist .event .commands a{margin:0 3px}.dir-rtl.path-calendar .cal_courses_flt label{margin-right:0;margin-left:.45em}.dir-rtl.path-calendar .maincalendar .calendar_event_course,.dir-rtl.path-calendar .maincalendar .calendar_event_global,.dir-rtl.path-calendar .maincalendar .calendar_event_group,.dir-rtl.path-calendar .maincalendar .calendar_event_user{border-right-width:12px;border-left-width:1px}.dir-rtl.path-calendar .maincalendar .calendar-controls .next{text-align:left}.dir-rtl.path-calendar .maincalendar .calendar-controls .previous{text-align:right}.dir-rtl.path-calendar .maincalendar .calendartable td,.dir-rtl.path-calendar .maincalendar .calendartable li{text-align:right}.dir-rtl.path-calendar .maincalendar .calendartable li{padding-right:10px;padding-left:5px}.dir-rtl.path-calendar .maincalendar .header .buttons{float:left}.dir-rtl.path-calendar .maincalendar .eventlist .event .topic .name{float:right}.dir-rtl.path-calendar .maincalendar .eventlist .event .topic .date{float:left}.dir-rtl.path-calendar .maincalendar .eventlist .event .description .commands{right:inherit;left:0}.dir-rtl.path-calendar .maincalendar .eventlist .event .course,.dir-rtl.path-calendar .maincalendar .eventlist .event .subscription{float:right;clear:right}#page-calendar-export .indent{padding-left:20px}.block .minicalendar{width:100%;max-width:280px;margin:0 auto}.block .minicalendar th,.block .minicalendar td{padding:2px;font-size:.8em;text-align:center}.block .minicalendar td.weekend{color:#999}.block .calendar-event-panel{background-color:#eee;border:1px solid #eee}.block .calendar-event-panel .yui3-overlay-content{padding:19px;background-color:#fdfdfd;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)}.block .calendar-event-panel .yui3-overlay-content h2.eventtitle{font-size:18px;line-height:1.2}.block .calendar-event-panel .yui3-overlay-content .eventcontent img{padding-right:5px}.block .calendar-controls .previous,.block .calendar-controls .current,.block .calendar-controls .next{display:block;float:left}.block .calendar-controls .previous{width:12%;text-align:left}.block .calendar-controls .current{width:76%;text-align:center}.block .calendar-controls .next{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}.block .content h3.eventskey{margin-top:.5em}.dir-rtl .block .calendar_filters .eventname{padding-right:.2em;padding-left:0}.dir-rtl .block .calendar-event-panel .yui3-overlay-content .eventcontent img{padding-right:0;padding-left:5px}@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}.sitetopic ul.section{margin:0}.course-content ul.section{margin:1em}.section .spinner{width:16px;height:16px}.section .activity .spinner{position:absolute;left:100%;vertical-align:text-bottom}.section .activity .editing_move{position:absolute;top:0;left:0}.section .activity .mod-indent-outer{padding-left:32px}.section .activity .actions{position:absolute;top:0;right:0}.section .activity .contentwithoutlink,.section .activity .activityinstance{display:table-cell;min-width:40%;min-height:2em;padding-right:4px}.section .activity .contentwithoutlink .dimmed img.activityicon,.section .activity .activityinstance .dimmed img.activityicon{opacity:.5;filter:alpha(opacity=50)}.section .label .contentwithoutlink,.section .label .activityinstance{display:block;height:inherit;padding-right:32px}.section .label .mod-indent-outer{display:block;padding-left:24px}.section .filler{display:inline-block;width:16px;height:16px;padding:.3em}.section .activity.editor_displayed a.editing_title,.section .activity.editor_displayed .moodle-actionmenu{display:none}.section .activity.editor_displayed div.activityinstance{padding-right:initial}.section .activity.editor_displayed div.activityinstance input{padding-top:initial;padding-bottom:initial;margin-bottom:initial;vertical-align:text-bottom}.dir-rtl .section .activity .spinner{right:100%;left:auto}.dir-rtl .section .activity .mod-indent-outer{padding-right:32px;padding-left:initial}.dir-rtl .section .activity .actions{right:auto;left:0}.dir-rtl .section .activity .contentwithoutlink,.dir-rtl .section .activity .activityinstance{padding-right:initial;padding-left:4px}.dir-rtl.editing .section .activity .editing_move{right:0;left:auto}.dir-rtl.editing .section .activity.editor_displayed div.activityinstance{padding-left:initial}.activity img.activityicon{margin-right:6px;vertical-align:text-bottom}.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 .contentwithoutlink,.editing .section .activity .activityinstance{padding-right:200px}.dir-rtl.editing .section .activity .contentwithoutlink,.dir-rtl.editing .section .activity .activityinstance{padding-right:0;padding-left:200px}.editing_show+.editing_assign,.editing_hide+.editing_assign{margin-left:20px}.section .activity .commands{display:inline;white-space:nowrap}.section .activity.modtype_label.label{padding:.2em;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 #ddd}.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}#site-news-forum h2,#frontpage-course-list h2,#frontpage-category-names h2,#frontpage-category-combo h2{margin-bottom:9px}.path-course-view a.reduce-sections{padding-left:.2em}.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;padding:0 16px 0 0}.dir-rtl.path-site li.activity>div,.dir-rtl.path-course-view li.activity>div{position:relative;padding:0 0 0 16px}.path-course-view li.activity span.autocompletion img{vertical-align:text-bottom}.path-course-view li.activity form.togglecompletion img{max-width:none}.path-course-view li.activity form.togglecompletion .ajaxworking{position:absolute;top:3px;right:22px;width:16px;height:16px;background:url([[pix:i/ajaxloader]]) no-repeat}.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}.single-section h3.sectionname{clear:both;text-align:center}.section img.movetarget{width:80px;height:16px}input.titleeditor{width:330px;vertical-align:text-bottom}span.editinstructions{position:absolute;top: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}#dndupload-status{position:fixed;left:0;z-index:1;width:40%;padding:6px;margin:0 30%;color:#3a87ad;text-align:center;background:#d9edf7;border:1px solid #bce8f1;-webkit-border-radius:8px;-moz-border-radius:8px;border-radius: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>.coursename a{display:block;padding-left:21px;background-image:url([[pix:moodle|i/course]]);background-position:left .5em;background-repeat:no-repeat}.dir-rtl .coursebox>.info>.coursename a{padding-right:21px;padding-left:0;background-position:right}.coursebox.remotehost>.info>.categoryname a{background-image:url([[pix:moodle|i/mnethost]])}.coursebox .content .teachers,.coursebox .content .courseimage,.coursebox .content .coursefile{float:left;width:40%;clear:left}.dir-rtl .coursebox>.info>.coursename,.dir-rtl .coursebox .teachers,.dir-rtl .coursebox .content .courseimage,.dir-rtl .coursebox .content .coursefile{float:right;clear:right}.coursebox>.info>h3.coursename{margin:5px}.coursebox>.info>.coursename{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>.categoryname a{padding-right:21px;padding-left:0;background-position:center right}.dir-rtl .coursebox>.info>.categoryname,.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:5px;border:1px solid #ddd}.courses .coursebox.even{background-color:#f9f9f9}.courses .coursebox:hover,.course_category_tree .courses>.paging.paging-morelink:hover{background-color:#f5f5f5}.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>.categoryname{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>.categoryname{background-image:url([[pix:moodle|t/collapsed_empty_rtl]]);background-position:center right}.course_category_tree .category.with_children>.info>.categoryname{cursor:pointer;background-image:url([[pix:moodle|t/expanded]])}.course_category_tree .category.with_children.collapsed>.info>.categoryname{background-image:url([[pix:moodle|t/collapsed]])}.dir-rtl .course_category_tree .category.with_children.collapsed>.info>.categoryname{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;clear:both;background-color:#f5f5f5;border:1px solid #e3e3e3;border-color:#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 .summary .iconsmall,.section .activity .iconsmall{width:16px;height:16px}.section .editing_title .iconsmall{width:12px;height:12px;padding:4px 8px 0 0;margin:8px 8px 0 0;vertical-align:text-bottom}.section .moodle-actionmenu .iconsmall{width:16px;height:16px;max-width:none!important;padding:4px;vertical-align:text-bottom}.section .moodle-actionmenu[data-enhanced] .menu img{width:12px;height:12px}.dir-rtl .section .editing_title .iconsmall{padding:4px 0 0 8px;margin:8px 0 0 8px}#course-category-listings{margin-bottom:200px;background-color:transparent}#course-category-listings.columns-2>#course-listing>div{position:relative;left:-1px}#course-category-listings.columns-3>#course-listing>div{height:100%}#course-category-listings>div>div{min-height:300px}#course-category-listings>div>div>ul.ml>li:first-child>div{border-top:0}#course-category-listings h3{padding:.4rem .6rem .3rem;margin:0}#course-category-listings h4{padding:.6rem 1rem .5rem;margin:1rem 0 0}#course-category-listings .moodle-actionmenu{white-space:nowrap}#course-category-listings .moodle-actionmenu[data-enhance] .toggle-display img{width:auto}#course-category-listings .moodle-actionmenu[data-enhance] .toggle-display.textmenu{padding-right:4px}#course-category-listings .moodle-actionmenu[data-enhance] .toggle-display.textmenu .caret{margin-top:12px}#course-category-listings .listing-actions{padding:.4rem .3rem .3rem;line-height:2.2em;text-align:center}#course-category-listings .listing-actions>.moodle-actionmenu{display:inline-block}#course-category-listings .listing-actions>.moodle-actionmenu .menu a{padding-left:1rem}#course-category-listings .listing-actions .moodle-actionmenu:not([data-enhanced]) li{line-height:normal}#course-category-listings .listing-actions .moodle-actionmenu:not([data-enhanced])>.menubar a{display:inline-block;color:inherit}#course-category-listings .listing-actions .moodle-actionmenu:not([data-enhanced])>.menubar a>img{display:none}#course-category-listings .listing-actions .moodle-actionmenu:not([data-enhanced])>.menubar a .caret{display:none}#course-category-listings .listing-actions .moodle-actionmenu:not([data-enhanced])>.menu .menu-action-text{display:inline-block}#course-category-listings ul.ml{margin:1rem 0;list-style:none}#course-category-listings ul.ml ul.ml{margin:0}#course-category-listings li{line-height:2.2em}#course-category-listings li>div:hover{background-color:#f5f5f5}#course-category-listings li .tree-icon{width:12px;margin:2px 6px 0 0;vertical-align:inherit}#course-category-listings li[data-selected='1']>div{background-color:#f9f9f9}#course-category-listings li[data-selected='1']>div:hover{background-color:#f5f5f5}#course-category-listings li .tree-icon{margin-left:0}#course-category-listings li li .tree-icon{margin-left:1em}#course-category-listings li li li .tree-icon{margin-left:2em}#course-category-listings li li li li .tree-icon{margin-left:3em}#course-category-listings li li li li li .tree-icon{margin-left:4em}#course-category-listings li li li li li li .tree-icon{margin-left:4.5em}#course-category-listings li li li li li li li .tree-icon{margin-left:5em}#course-category-listings li li li li li li li li .tree-icon{margin-left:5.5em}#course-category-listings .item-actions{display:inline-block;display:initial;margin-right:1em}#course-category-listings .item-actions img{height:12px;padding:0;margin:0 4px;vertical-align:inherit}#course-category-listings .item-actions.show .menu a{padding:4px 1em 4px 4px}#course-category-listings .item-actions.show .menu img{width:12px;max-width:none}#course-category-listings .item-actions .menu-action-text{vertical-align:inherit}#course-category-listings .listitem>div>.float-left{float:left}#course-category-listings .listitem>div>.float-right{float:right;text-align:right}#course-category-listings .listitem>div .item-actions .action-show{display:none}#course-category-listings .listitem>div .item-actions .action-hide{display:inline}#course-category-listings .listitem>div .without-actions{color:#333}#course-category-listings .listitem>div .idnumber{margin-right:2em;color:#a1a1a8}#course-category-listings .listitem[data-visible="0"]{color:#999}#course-category-listings .listitem[data-visible="0"]>div>a{color:#999}#course-category-listings .listitem[data-visible="0"]>div .item-actions .action-show{display:inline}#course-category-listings .listitem[data-visible="0"]>div .item-actions .action-hide{display:none}#course-category-listings .listitem.highlight{background-color:transparent}#course-category-listings .listitem.highlight>div,#course-category-listings .listitem.highlight>div:hover,#course-category-listings .listitem.highlight[data-selected='1']>div{background-color:#f5f5f5}#course-category-listings #course-listing .listitem .categoryname{display:inline-block;margin-left:1em;color:#a1a1a8}#course-category-listings #course-listing .listitem .coursename{display:inline-block}#course-category-listings #course-listing .listitem>div{padding-left:1rem}#course-category-listings #course-listing>.firstpage .listitem:first-child>div .item-actions .action-moveup,#course-category-listings #course-listing>.lastpage .listitem:last-child>div .item-actions .action-movedown{display:none}#course-category-listings #course-listing .bulk-action-checkbox{margin:-2px 6px 0 0}#course-category-listings #category-listing .listitem.collapsed>ul.ml{display:none}#course-category-listings #category-listing .listitem>div>.ba-checkbox{width:2.2em;padding-top:2px;margin:-1px .5em 0 0;text-align:center}#course-category-listings #category-listing .listitem.highlight>div>.ba-checkbox{background-color:#f5f5f5}#course-category-listings #category-listing .listitem[data-selected='1']>div>.ba-checkbox{padding:0;margin:0 .5em 0 0;background-color:inherit}#course-category-listings #category-listing .listitem:first-child>div .item-actions .action-moveup,#course-category-listings #category-listing .listitem:last-child>div .item-actions .action-movedown{display:none}#course-category-listings #category-listing .course-count{display:inline-block;min-width:3.5em;margin-right:2rem;color:#a1a1a8}#course-category-listings #category-listing .course-count .smallicon{width:12px;margin-left:4px;vertical-align:inherit}#course-category-listings #category-listing .bulk-action-checkbox{margin-right:-3px}#course-category-listings #category-listing .category-listing>ul>.listitem:first-child{position:relative}#course-category-listings #category-listing .category-bulk-actions{position:relative;margin:0 .5em .5em}#course-category-listings .detail-pair{margin:0 1rem;border-bottom:1px solid #ddd}#course-category-listings .detail-pair>*{display:inline-block;line-height:2.2rem}#course-category-listings .detail-pair .pair-key{font-weight:bold;vertical-align:top}#course-category-listings .detail-pair .pair-key span{display:block;margin-right:1rem}#course-category-listings .detail-pair .pair-value select{max-width:100%}#course-category-listings .bulk-actions .detail-pair>*{display:block;width:100%}#course-category-listings .listing-pagination{text-align:center}#course-category-listings .listing-pagination .yui3-button{margin:.4rem .2rem .45rem;font-size:10.4px;background-color:#fff;border:0}#course-category-listings .listing-pagination .yui3-button.active-page{background-color:#e6e6e6}#course-category-listings .listing-pagination-totals{text-align:center}#course-category-listings .listing-pagination-totals.dimmed{margin:.4rem 1rem .45rem;color:#999}#course-category-listings .select-a-category .notifymessage,#course-category-listings .select-a-category .alert{margin:1em}#course-category-listings #course-listing .listitem .drag-handle{display:none}.jsenabled #course-category-listings #course-listing .listitem .drag-handle{display:inline-block;margin:0 6px 0 0;cursor:pointer}.dir-rtl #course-category-listings #category-listing,.dir-rtl #course-category-listings #course-listing{float:right;margin-left:0}.dir-rtl #course-category-listings .listitem>div>.float-left{float:right}.dir-rtl #course-category-listings .listitem>div>.float-right{float:left;text-align:left}.dir-rtl #course-category-listings li .tree-icon{margin:2px 0 0 6px}.dir-rtl #course-category-listings li .tree-icon{margin-right:0}.dir-rtl #course-category-listings li li .tree-icon{margin-right:1em}.dir-rtl #course-category-listings li li li .tree-icon{margin-right:2em}.dir-rtl #course-category-listings li li li li .tree-icon{margin-right:3em}.dir-rtl #course-category-listings li li li li li .tree-icon{margin-right:4em}.dir-rtl #course-category-listings li li li li li li .tree-icon{margin-right:4.5em}.dir-rtl #course-category-listings li li li li li li li .tree-icon{margin-right:5em}.dir-rtl #course-category-listings li li li li li li li li .tree-icon{margin-right:5.5em}.dir-rtl #course-category-listings #category-listing .listitem>div{margin-right:.5em;margin-left:0}.dir-rtl #course-category-listings #category-listing .listitem>div>.ba-checkbox{margin:-1px 0 0 .5em}.dir-rtl #course-category-listings #category-listing .listitem[data-selected='1']>div>.ba-checkbox{margin:0 0 0 .5em}.dir-rtl #course-category-listings #category-listing .course-count{margin-left:2rem}.dir-rtl #course-category-listings #category-listing .bulk-action-checkbox{margin-right:0;margin-left:-3px}.dir-rtl #course-category-listings #course-listing{padding-right:24px}.dir-rtl #course-category-listings #course-listing .listitem .idnumber{padding-right:2em;color:#a1a1a8}.dir-rtl #course-category-listings #course-listing .listitem .categoryname{display:inline-block;margin-right:1em;margin-left:0}.dir-rtl #course-category-listings #course-listing .listitem .drag-handle{margin:0 6px 0 6px}.dir-rtl #course-category-listings #course-listing .listitem>div{padding-left:1rem}.dir-rtl #course-category-listings #course-listing .bulk-action-checkbox{margin:-2px 0 0 6px;vertical-align:middle}.dir-rtl #course-category-listings .detail-pair>*{float:right;margin-right:0}.dir-rtl #course-category-listings .detail-pair .pair-key span{margin-right:0;margin-left:0}.dir-rtl #course-category-listings .detail-pair .pair-value{margin-right:.5em}.coursecat-management-header{vertical-align:middle}.coursecat-management-header h2{display:inline-block;text-align:left}.coursecat-management-header>div{display:inline-block;float:right;line-height:40px}.coursecat-management-header>div>div{display:inline-block;margin:10px 0;margin-left:1em}.coursecat-management-header select{max-width:300px;padding:.4em .5em .45em 1em;white-space:nowrap;vertical-align:baseline;cursor:pointer}.coursecat-management-header .view-mode-selector .moodle-actionmenu{display:inline-block;white-space:nowrap}.coursecat-management-header .view-mode-selector .moodle-actionmenu[data-enhanced].show .menu a{padding-left:1em}.dir-rtl .coursecat-management-header h2{text-align:right}.dir-rtl .coursecat-management-header>div{float:left;margin-right:1em;margin-left:0}.course-being-dragged-proxy{padding:0 0 0 4em;color:#0070a8;vertical-align:middle;border:0}.course-being-dragged{opacity:.5;filter:alpha(opacity=50)}@media(min-width:1200px) and (max-width:1600px){#course-category-listings.columns-3{background-color:transparent;border:0}#course-category-listings.columns-3 #category-listing,#course-category-listings.columns-3 #course-listing{width:50%}#course-category-listings.columns-3 #category-listing>div,#course-category-listings.columns-3 #course-listing>div,#course-category-listings.columns-3 #course-detail>div{background-color:transparent}#course-category-listings.columns-3 #course-detail{width:100%;margin-top:1em}}@media(max-width:1199px){#course-category-listings.columns-2,#course-category-listings.columns-3{background-color:transparent;border:0}#course-category-listings.columns-2 #category-listing,#course-category-listings.columns-3 #category-listing,#course-category-listings.columns-2 #course-listing,#course-category-listings.columns-3 #course-listing,#course-category-listings.columns-2 #course-detail,#course-category-listings.columns-3 #course-detail{width:100%;margin:0 0 1em}#course-category-listings.columns-2 #category-listing>div,#course-category-listings.columns-3 #category-listing>div,#course-category-listings.columns-2 #course-listing>div,#course-category-listings.columns-3 #course-listing>div,#course-category-listings.columns-2 #course-detail>div,#course-category-listings.columns-3 #course-detail>div{background-color:transparent}}.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}.filemanager .fp-license td,.file-picker .fp-setlicense td{max-width:265px}.filemanager .fp-license select,.file-picker .fp-setlicense select{max-width:100%}.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}.moodle-dialogue-base .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:40px;background:#f2f2f2;border-bottom:1px solid #bbb}.file-picker .fp-navbar .fp-viewbar{margin:4px}.file-picker .fp-content{height:452px;overflow:auto;clear:both;background:#fff}.filepicker.moodle-dialogue-fullscreen .file-picker .fp-content{width:100%}.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:4px}.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{width:16px;height:16px;padding:0 7px 0 5px}.fp-toolbar{float:left}.dir-rtl .fp-toolbar{float:right}.fp-toolbar.empty{display:none}.dir-rtl .fp-toolbar div.disabled,.fp-toolbar .disabled{display:none}.fp-toolbar div{display:block;float:left;margin-right:4px}.dir-rtl .fp-toolbar div{display:block;float:right;margin-right:0;margin-left:4px}.fp-toolbar img{margin-right:5px;vertical-align:-15%}.fp-toolbar .fp-tb-search{width:235px;height:27px}.fp-toolbar .fp-tb-search input{width:200px;height:27px;padding:2px 6px 1px 27px;background:#fff url('[[pix:a/search]]') no-repeat 7px 7px;border:1px solid #bbb}.fp-viewbar{float:right;height:30px;background:white;border:1px solid #CCC;border-bottom:1px solid #b3b3b3;border-radius:4px}.fp-repo-items fp-viewbar{margin:4px}.dir-rtl .fp-toolbar img{vertical-align:-35%}.dir-rtl .fp-viewbar{float:left}.fp-viewbar a{display:block;float:left;width:30px;height:30px;border-right:1px solid #CCC}.fp-viewbar a.checked:hover,.fp-viewbar a:hover{background-color:#ebebeb;background-image:radial-gradient(ellipse at center,#fff 60%,#dfdfdf 100%)}.fp-viewbar a.checked,.fp-viewbar a:active{background-color:#dfdfdf;background-image:radial-gradient(ellipse at center,#fff 40%,#dfdfdf 100%)}.fp-viewbar a.fp-vb-icons{border-radius:4px 0 0 4px}.fp-viewbar a.fp-vb-tree{border-right:0;border-radius:0 4px 4px 0}.fp-viewbar a img{margin:7px}.fp-viewbar.disabled a{cursor:default;background:0;opacity:.45}.file-picker .fp-clear-left{clear:left}.dir-rtl .filemanager-toolbar .fp-vb-icons a:hover{background:url('[[pix:theme|fp/view_icon_selected]]')}.dir-rtl .filemanager-toolbar .fp-vb-icons.checked a:hover{background:url('[[pix:theme|fp/view_icon_active]]') no-repeat 0 0}.dir-rtl .fp-vb-details a:hover{background:0;border:20px solid black}.dir-rtl .fp-vb-details.checked a:hover{background:0;border:40px solid black}.dir-rtl .fp-vb-tree a:hover{background:0;border:30px solid black}.dir-rtl .fp-vb-tree.checked a:hover{background:0;border:50px solid black}.file-picker .fp-pathbar{display:table-row}.fp-pathbar.empty{display:none}.fp-pathbar .fp-path-folder{width:27px;height:12px;margin-left:4px;background:url('[[pix:theme|fp/path_folder]]') no-repeat 0 0}.dir-rtl .fp-pathbar .fp-path-folder{width:auto;height:12px;margin-left:4px;background:url('[[pix:theme|fp/path_folder_rtl]]') no-repeat right top}.dir-rtl .fp-pathbar span{display:inline-block;*display:inline;float:right;margin-left:32px;*zoom:1}.fp-pathbar .fp-path-folder-name{margin-left:32px;line-height:20px}.dir-rtl .fp-pathbar .fp-path-folder-name{margin-right:32px;line-height:20px}.fp-iconview .fp-file{position:relative;float:left;margin:10px 10px 35px;text-align:center}.fp-iconview .fp-thumbnail{display:block;min-width:110px;min-height:110px;line-height:110px;text-align:center;border:1px solid #fff}.fp-iconview .fp-thumbnail img{padding:3px;vertical-align:middle;border:1px solid #ddd;-webkit-box-shadow:1px 1px 2px 0 #ccc;-moz-box-shadow:1px 1px 2px 0 #ccc;box-shadow:1px 1px 2px 0 #ccc}.fp-iconview .fp-thumbnail:hover{background:#fff;border:1px solid #ddd;-webkit-box-shadow:inset 0 0 10px 0 #ccc;-moz-box-shadow:inset 0 0 10px 0 #ccc;box-shadow:inset 0 0 10px 0 #ccc}.fp-iconview .fp-filename-field{position:absolute;height:33px;overflow:hidden;word-wrap:break-word}.fp-iconview .fp-filename-field:hover{z-index:1000;overflow:visible}.fp-iconview .fp-filename-field .fp-filename{min-width:112px;padding-top:5px;padding-bottom:12px;background:#fff}.dir-rtl .fp-iconview .fp-file{float:right}.file-picker .yui3-datatable table{width:100%;border:0 solid #bbb}#filesskin .file-picker .yui3-datatable-header{color:#555;background:#fff;border-bottom:1px solid #ccc;border-left:0 solid #fff}#filesskin .file-picker .yui3-datatable-odd .yui3-datatable-cell{background-color:#f6f6f6;border-left:0 solid #f6f6f6}#filesskin .file-picker .yui3-datatable-even .yui3-datatable-cell{background-color:#fff;border-left:0 solid #fff}.dir-rtl .file-picker .yui3-datatable-header{text-align:right}.file-picker .ygtvtn,.filemanager .ygtvtn{width:17px;height:22px;background:url('[[pix:moodle|y/tn]]') 0 0 no-repeat}.dir-rtl .filemanager .ygtvtn,.dir-rtl .file-picker .ygtvtn{width:17px;height:22px;background:url('[[pix:moodle|y/tn_rtl]]') 0 0 no-repeat}.file-picker .ygtvtm,.filemanager .ygtvtm{width:13px;height:12px;cursor:pointer;background:url('[[pix:moodle|y/tm]]') 0 10px no-repeat}.file-picker .ygtvtmh,.filemanager .ygtvtmh{width:13px;height:12px;cursor:pointer;background:url('[[pix:moodle|y/tm]]') 0 10px no-repeat}.file-picker .ygtvtp,.filemanager .ygtvtp{width:13px;height:12px;cursor:pointer;background:url('[[pix:moodle|y/tp]]') 0 10px no-repeat}.dir-rtl .file-picker .ygtvtp,.dir-rtl .filemanager .ygtvtp{background:url('[[pix:moodle|y/tp_rtl]]') 0 10px no-repeat}.file-picker .ygtvtph,.filemanager .ygtvtph{width:13px;height:22px;cursor:pointer;background:url('[[pix:moodle|y/tp]]') 0 10px no-repeat}.dir-rtl .file-picker .ygtvtph,.dir-rtl .filemanager .ygtvtph{background:url('[[pix:moodle|y/tp_rtl]]') 0 10px no-repeat}.file-picker .ygtvln,.filemanager .ygtvln{width:17px;height:22px;background:url('[[pix:moodle|y/ln]]') 0 0 no-repeat}.dir-rtl .file-picker .ygtvln,.dir-rtl .filemanager .ygtvln{background:url('[[pix:moodle|y/ln_rtl]]') 0 0 no-repeat}.file-picker .ygtvlm,.filemanager .ygtvlm{width:13px;height:12px;cursor:pointer;background:url('[[pix:moodle|y/lm]]') 0 10px no-repeat}.file-picker .ygtvlmh,.filemanager .ygtvlmh{width:13px;height:12px;cursor:pointer;background:url('[[pix:moodle|y/lm]]') 0 10px no-repeat}.file-picker .ygtvlp,.filemanager .ygtvlp{width:13px;height:12px;cursor:pointer;background:url('[[pix:moodle|y/lp]]') 0 10px no-repeat}.dir-rtl .file-picker .ygtvlp,.dir-rtl .filemanager .ygtvlp{background:url('[[pix:moodle|y/lp_rtl]]') 0 10px no-repeat}.file-picker .ygtvlph,.filemanager .ygtvlph{width:13px;height:12px;cursor:pointer;background:url('[[pix:moodle|y/lp]]') 0 10px no-repeat}.dir-rtl .file-picker .ygtvlph,.dir-rtl .filemanager .ygtvlph{background:url('[[pix:moodle|y/lp_rtl]]') 0 10px no-repeat}.file-picker .ygtvloading,.filemanager .ygtvloading{width:16px;height:22px;background:transparent url('[[pix:moodle|y/loading]]') 0 0 no-repeat}.file-picker .ygtvdepthcell,.filemanager .ygtvdepthcell{width:17px;height:32px;background:url('[[pix:moodle|y/vline]]') 0 0 no-repeat}.file-picker .ygtvblankdepthcell,.filemanager .ygtvblankdepthcell{width:17px;height:22px}a.ygtvspacer:hover{color:transparent;text-decoration:none}.ygtvlabel,.ygtvlabel:link,.ygtvlabel:visited,.ygtvlabel:hover{margin-left:2px;text-decoration:none;cursor:pointer;background-color:transparent}.file-picker .ygtvfocus,.filemanager .ygtvfocus{background-color:#eee}.fp-filename-icon{position:relative;display:block;margin-top:10px}.fp-icon{float:left;width:24px;height:24px;margin-top:-7px;margin-right:10px;line-height:24px;text-align:center}.dir-rtl .fp-icon{float:right;margin-right:0;margin-left:10px}.fp-icon img{max-width:24px;max-height:24px;vertical-align:middle}.fp-filename{padding-right:10px}.dir-rtl .fp-filename{padding-right:0;padding-left:10px}.file-picker .fp-login-form{display:table;width:100%;height:100%}.file-picker .fp-login-form table{margin:0 auto}.file-picker .fp-login-form p{margin-top:3em;text-align:center}.file-picker .fp-login-form .fp-login-input label{display:block;text-align:right}.file-picker .fp-login-form .fp-login-input .input{text-align:left}.file-picker .fp-login-form input[type="checkbox"]{width:15px;height:15px}.file-picker .fp-upload-form{display:table;width:100%;height:100%}.file-picker .fp-upload-form table{margin:0 auto}.file-picker.fp-dlg{text-align:center}.file-picker.fp-dlg .fp-dlg-text{padding:30px 20px 10px;font-size:12px}.file-picker.fp-dlg .fp-dlg-buttons{margin:0 20px}.file-picker.fp-msg{text-align:center}.file-picker.fp-msg .fp-msg-text{max-width:500px;max-height:300px;min-width:200px;padding:40px 20px 10px 20px;overflow:auto;font-size:12px}.file-picker.fp-msg.fp-msg-error .fp-msg-text{padding:40px 20px 10px 20px;font-size:12px}.file-picker .fp-content-error{display:table;width:100%;height:100%;text-align:center}.file-picker .fp-content-error .fp-error{display:table-cell;width:100%;height:100%;padding:40px 20px 10px 20px;font-size:12px;vertical-align:middle}.file-picker .fp-nextpage{clear:both}.file-picker .fp-nextpage .fp-nextpage-loading{display:none}.file-picker .fp-nextpage.loading .fp-nextpage-link{display:none}.file-picker .fp-nextpage.loading .fp-nextpage-loading{display:block;height:100px;padding-top:50px;text-align:center}.fp-select form{padding:20px 20px 0}.fp-select .fp-select-loading{margin-top:20px;text-align:center}.fp-select .fp-hr{width:auto;height:1px;margin:10px 0;clear:both;background-color:#fff;border-bottom:1px solid #bbb}.fp-select table{padding:0 0 10px}.fp-select table .mdl-right{min-width:84px}.fp-select .fp-reflist .mdl-right{vertical-align:top}.fp-select .fp-select-buttons{float:right}.fp-select .fp-info{display:block;padding:1px 20px 0;clear:both}.fp-select .fp-thumbnail{float:left;min-width:110px;min-height:110px;margin:10px 20px 0 0;line-height:110px;text-align:center;background:#fff;border:1px solid #ddd;-webkit-box-shadow:inset 0 0 10px 0 #ccc;-moz-box-shadow:inset 0 0 10px 0 #ccc;box-shadow:inset 0 0 10px 0 #ccc}.fp-select .fp-thumbnail img{padding:3px;margin:10px;vertical-align:middle;border:1px solid #ddd}.fp-select .fp-fileinfo{display:inline-block;*display:inline;margin-top:10px;*zoom:1}.file-picker.fp-select .fp-fileinfo{max-width:240px}.fp-select .fp-fileinfo div{padding-bottom:5px}.file-picker.fp-select .uneditable{display:none}.file-picker.fp-select .fp-select-loading{display:none}.file-picker.fp-select.loading .fp-select-loading{display:block}.file-picker.fp-select.loading form{display:none}.fp-select .fp-dimensions.fp-unknown{display:none}.filemanager-loading{display:none}.jsenabled .filemanager-loading{display:block;margin-top:100px}.filemanager.fm-loading .filemanager-toolbar,.filemanager.fm-loading .fp-pathbar,.filemanager.fm-loading .filemanager-container,.filemanager.fm-loaded .filemanager-loading,.filemanager.fm-maxfiles .fp-btn-add,.filemanager.fm-maxfiles .dndupload-message,.filemanager.fm-noitems .fp-btn-download,.filemanager .fm-empty-container,.filemanager.fm-noitems .filemanager-container .fp-content{display:none}.filemanager .fp-img-downloading{display:none;padding-top:7px}.filemanager .filemanager-updating{display:none;text-align:center}.filemanager.fm-updating .filemanager-updating{display:block;margin-top:37px}.filemanager.fm-updating .fm-content-wrapper,.filemanager.fm-nomkdir .fp-btn-mkdir,.fitem.disabled .filemanager .filemanager-toolbar,.fitem.disabled .filemanager .fp-pathbar,.fitem.disabled .filemanager .fp-restrictions,.fitem.disabled .filemanager .fm-content-wrapper{display:none}.filemanager .fp-restrictions{text-align:right}.filemanager .fp-navbar{background:#f2f2f2;border:1px solid #bbb;border-bottom:0}.filemanager-toolbar{padding:4px;overflow:hidden}.fp-pathbar{min-height:20px;padding:5px 8px 1px;border-top:1px solid #bbb}.file-picker .fp-toolbar{padding:4px}.fp-toolbar .fp-btn-add,.fp-toolbar .fp-btn-download,.fp-toolbar .fp-btn-mkdir,.fp-toolbar .fp-tb-help,.fp-toolbar .fp-tb-manage,.fp-toolbar .fp-tb-logout,.fp-toolbar .fp-tb-refresh{width:30px;height:30px;background:white;border:1px solid #CCC;border-bottom:1px solid #b3b3b3;border-radius:4px}.fp-toolbar a:hover{background-color:#ebebeb;background-image:radial-gradient(ellipse at center,#fff 60%,#dfdfdf 100%)}.fp-toolbar a:active{background-color:#dfdfdf;background-image:radial-gradient(ellipse at center,#fff 40%,#dfdfdf 100%)}.fp-btn-add a,.fp-btn-download a,.fp-btn-mkdir a,.fp-tb-help a,.fp-tb-manage a,.fp-tb-logout a,.fp-tb-refresh a{display:block;width:30px;height:30px;border-radius:4px}.fp-btn-add img,.fp-btn-download img,.fp-btn-mkdir img,.fp-tb-help img,.fp-tb-manage img,.fp-tb-logout img,.fp-tb-refresh img{margin:7px}.filemanager .fp-pathbar.empty{display:none}.filepicker-filelist,.filemanager-container{position:relative;min-height:140px;overflow:auto;clear:both;background:#fff;border:1px solid #bbb}.filemanager .fp-content{max-height:472px;min-height:157px;overflow:auto}.filemanager-container,.filepicker-filelist{overflow:hidden}.fitem.disabled .filepicker-filelist,.fitem.disabled .filemanager-container{background-color:#ebebe4}.fitem.disabled .fp-btn-choose{color:#999}.fitem.disabled .filepicker-filelist .filepicker-filename{display:none}.fp-iconview .fp-reficons1{position:absolute;top:0;left:0;width:100%;height:100%}.fp-iconview .fp-reficons2{position:absolute;top:0;left:0;width:100%;height:100%}.fp-iconview .fp-file.fp-hasreferences .fp-reficons1{background:url('[[pix:theme|fp/link]]') no-repeat;background-position:bottom right}.fp-iconview .fp-file.fp-isreference .fp-reficons2{background:url('[[pix:theme|fp/alias]]') no-repeat;background-position:bottom left}.filemanager .fp-iconview .fp-file.fp-originalmissing .fp-thumbnail img{display:none}.filemanager .fp-iconview .fp-file.fp-originalmissing .fp-thumbnail{background:url([[pix:s/dead]]) no-repeat;background-position:center center}.filemanager .yui3-datatable table{width:100%;border:0 solid #bbb}.filemanager .yui3-datatable-header{color:#555!important;background:#fff!important;border-bottom:1px solid #ccc!important;border-left:0 solid #fff!important}.filemanager .yui3-datatable-odd .yui3-datatable-cell{background-color:#f6f6f6!important;border-left:0 solid #f6f6f6}.filemanager .yui3-datatable-even .yui3-datatable-cell{background-color:#fff!important;border-left:0 solid #fff}.filemanager .fp-filename-icon.fp-hasreferences .fp-reficons1{position:absolute;top:8px;left:17px;z-index:1000;width:100%;height:100%;background:url('[[pix:theme|fp/link_sm]]') no-repeat 0 0}.filemanager .fp-filename-icon.fp-isreference .fp-reficons2{position:absolute;top:9px;left:-6px;z-index:1001;width:100%;height:100%;background:url('[[pix:theme|fp/alias_sm]]') no-repeat 0 0}.filemanager .fp-contextmenu{display:none}.filemanager .fp-iconview .fp-folder.fp-hascontextmenu .fp-contextmenu{position:absolute;right:7px;bottom:5px;display:block}.filemanager .fp-treeview .fp-folder.fp-hascontextmenu .fp-contextmenu,.filemanager .fp-tableview .fp-folder.fp-hascontextmenu .fp-contextmenu{position:absolute;top:6px;left:14px;display:inline;margin-right:-20px}.dir-rtl .filemanager .fp-iconview .fp-folder.fp-hascontextmenu .fp-contextmenu{right:inherit;left:7px}.dir-rtl .filemanager .fp-treeview .fp-folder.fp-hascontextmenu .fp-contextmenu,.dir-rtl .filemanager .fp-tableview .fp-folder.fp-hascontextmenu .fp-contextmenu{right:16px;left:inherit;margin-right:0}.filepicker-filelist .filepicker-container,.filemanager.fm-noitems .fm-empty-container{position:absolute;top:10px;right:10px;bottom:10px;left:10px;display:block;padding-top:85px;text-align:center;border:2px dashed #bbb}.filepicker-filelist .dndupload-target,.filemanager-container .dndupload-target{position:absolute;top:10px;right:10px;bottom:10px;left:10px;padding-top:85px;text-align:center;background:#fff;border:2px dashed #fb7979;-webkit-box-shadow:0 0 0 10px #fff;-moz-box-shadow:0 0 0 10px #fff;box-shadow:0 0 0 10px #fff}.filepicker-filelist.dndupload-over .dndupload-target,.filemanager-container.dndupload-over .dndupload-target{position:absolute;top:10px;right:10px;bottom:10px;left:10px;padding-top:85px;text-align:center;background:#fff;border:2px dashed #6c8cd3}.dndupload-message{display:none}.dndsupported .dndupload-message{display:inline}.dnduploadnotsupported-message{display:none}.dndnotsupported .dnduploadnotsupported-message{display:inline}.dndupload-target{display:none}.dndsupported .dndupload-ready .dndupload-target{display:block}.dndupload-uploadinprogress{display:none;text-align:center}.dndupload-uploading .dndupload-uploadinprogress{display:block}.dndupload-arrow{position:absolute;top:5px;width:100%;height:80px;background:url([[pix:theme|fp/dnd_arrow]]) center no-repeat}.fitem.disabled .filepicker-container,.fitem.disabled .fm-empty-container{display:none}.dndupload-progressbars{display:none;padding:10px}.dndupload-inprogress .dndupload-progressbars{display:block}.dndupload-inprogress .fp-content{display:none}.filemanager.fm-noitems .dndupload-inprogress .fm-empty-container{display:none}.filepicker-filelist.dndupload-inprogress .filepicker-container{display:none}.filepicker-filelist.dndupload-inprogress a{display:none}.filemanager.fp-select .fp-select-loading{display:none}.filemanager.fp-select.loading .fp-select-loading{display:block}.filemanager.fp-select.loading form{display:none}.filemanager.fp-select.fp-folder .fp-license,.filemanager.fp-select.fp-folder .fp-author,.filemanager.fp-select.fp-file .fp-file-unzip,.filemanager.fp-select.fp-folder .fp-file-unzip,.filemanager.fp-select.fp-file .fp-file-zip,.filemanager.fp-select.fp-zip .fp-file-zip{display:none}.filemanager.fp-select .fp-file-setmain,.filemanager.fp-select .fp-file-setmain-help{display:none}.filemanager.fp-select.fp-cansetmain .fp-file-setmain,.filemanager.fp-select.fp-cansetmain .fp-file-setmain-help{display:inline-block;*display:inline;*zoom:1}.filemanager .fp-mainfile .fp-filename{font-weight:bold}.filemanager.fp-select.fp-folder .fp-file-download{display:none}.fm-operation{font-weight:bold}.filemanager.fp-select .fp-original.fp-unknown,.filemanager.fp-select .fp-original .fp-originloading{display:none}.filemanager.fp-select .fp-original.fp-loading .fp-originloading{display:inline}.filemanager.fp-select .fp-reflist.fp-unknown,.filemanager.fp-select .fp-reflist .fp-reflistloading{display:none}.filemanager.fp-select .fp-refcount{max-width:265px}.filemanager.fp-select .fp-reflist.fp-loading .fp-reflistloading{display:inline}.filemanager.fp-select .fp-reflist .fp-value{max-width:265px;max-height:75px;padding:8px 7px;margin:0;overflow:auto;background:#f9f9f9;border:1px solid #bbb}.filemanager.fp-select .fp-reflist .fp-value li{padding-bottom:7px}.filemanager.fp-mkdir-dlg{text-align:center}.filemanager.fp-mkdir-dlg .fp-mkdir-dlg-text{margin:20px;text-align:left}.dir-rtl .filemanager .fp-mkdir-dlg p{text-align:right}.filemanager.fp-dlg{text-align:center}.filemanager.fp-dlg .fp-dlg-text{max-width:340px;max-height:300px;min-width:200px;padding:0 10px;margin:40px 20px 20px;overflow:auto;font-size:12px;line-height:2