Merge branch 'MDL-53643_master' of git://github.com/dmonllao/moodle
authorDan Poltawski <dan@moodle.com>
Wed, 30 Mar 2016 07:52:26 +0000 (15:52 +0800)
committerDan Poltawski <dan@moodle.com>
Wed, 30 Mar 2016 07:52:26 +0000 (15:52 +0800)
95 files changed:
.gitattributes [new file with mode: 0644]
admin/settings.php
admin/tests/behat/filter_users.feature
admin/tool/messageinbound/classes/manager.php
admin/tool/monitor/tests/behat/disabled.feature
auth/mnet/auth.php
auth/mnet/upgrade.txt [new file with mode: 0644]
cohort/tests/behat/access_visible_cohorts.feature
cohort/tests/behat/add_cohort.feature
cohort/tests/behat/behat_cohort.php
cohort/tests/behat/view_cohorts.feature
config-dist.php
group/lib.php
lang/en/group.php
lang/en/search.php
lib/amd/build/form-autocomplete.min.js
lib/amd/src/form-autocomplete.js
lib/behat/form_field/behat_form_select.php
lib/classes/event/grouping_group_assigned.php [new file with mode: 0644]
lib/classes/event/grouping_group_unassigned.php [new file with mode: 0644]
lib/db/caches.php
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/src/editor/js/clean.js
lib/grade/grade_category.php
lib/modinfolib.php
lib/outputcomponents.php
lib/outputrenderers.php
lib/pagelib.php
message/ajax.php
mod/assign/classes/search/activity.php
mod/assign/tests/search_test.php [new file with mode: 0644]
mod/data/lib.php
mod/data/tests/search_test.php
mod/feedback/db/access.php
mod/feedback/db/upgrade.php
mod/feedback/import.php
mod/feedback/item/captcha/lib.php
mod/feedback/item/feedback_item_form_class.php
mod/feedback/item/info/lib.php
mod/feedback/item/label/label_form.php
mod/feedback/item/label/lib.php
mod/feedback/item/multichoice/lib.php
mod/feedback/item/multichoicerated/lib.php
mod/feedback/item/numeric/lib.php
mod/feedback/item/numeric/numeric_form.php
mod/feedback/item/textarea/lib.php
mod/feedback/item/textfield/lib.php
mod/feedback/item/textfield/textfield_form.php
mod/feedback/lang/en/feedback.php
mod/feedback/lib.php
mod/feedback/tests/behat/question_types.feature
mod/feedback/version.php
mod/folder/classes/search/activity.php
mod/folder/tests/search_test.php [new file with mode: 0644]
mod/forum/classes/search/post.php
mod/forum/lib.php
mod/forum/tests/mail_test.php
mod/forum/tests/search_test.php
mod/glossary/classes/search/entry.php
mod/page/classes/search/activity.php
mod/quiz/classes/structure.php
mod/quiz/db/upgrade.php
mod/quiz/tests/structure_test.php
mod/quiz/version.php
mod/resource/classes/search/activity.php
mod/resource/tests/search_test.php [new file with mode: 0644]
mod/scorm/datamodels/scorm_13.js
mod/scorm/datamodels/scorm_13lib.php
mod/url/classes/search/activity.php
repository/s3/README_MOODLE.txt
repository/s3/S3.php
repository/s3/lib.php
repository/s3/thirdpartylibs.xml
repository/s3/version.php
search/classes/area/base.php
search/classes/area/base_activity.php
search/classes/area/base_mod.php
search/classes/document.php
search/classes/engine.php
search/classes/manager.php
search/engine/solr/classes/document.php
search/engine/solr/classes/engine.php
search/engine/solr/classes/schema.php
search/engine/solr/lang/en/search_solr.php
search/engine/solr/settings.php
search/engine/solr/tests/engine_test.php
search/templates/result.mustache
search/tests/fixtures/mock_search_area.php
search/tests/fixtures/mock_search_engine.php
theme/base/style/search.css
theme/bootstrapbase/less/moodle/search.less
theme/bootstrapbase/style/moodle.css
user/lib.php

diff --git a/.gitattributes b/.gitattributes
new file mode 100644 (file)
index 0000000..fb39fe0
--- /dev/null
@@ -0,0 +1,4 @@
+**/yui/build/** -diff
+**/amd/build/** -diff
+theme/bootstrapbase/style/editor.css -diff
+theme/bootstrapbase/style/moodle.css -diff
index 76584b3..fda2359 100644 (file)
@@ -20,7 +20,11 @@ $adminroot = admin_get_root(); // need all settings
 $settingspage = $adminroot->locate($section, true);
 
 if (empty($settingspage) or !($settingspage instanceof admin_settingpage)) {
-    print_error('sectionerror', 'admin', "$CFG->wwwroot/$CFG->admin/");
+    if (moodle_needs_upgrading()) {
+        redirect(new moodle_url('/admin/index.php'));
+    } else {
+        print_error('sectionerror', 'admin', "$CFG->wwwroot/$CFG->admin/");
+    }
     die;
 }
 
index 8feb542..f8b930d 100644 (file)
@@ -27,7 +27,6 @@ Feature: An administrator can filter user accounts by role, cohort and other pro
     And I add "User Three (three@example.com)" user to "CH1" cohort members
     And I follow "Browse list of users"
 
-  @javascript
   Scenario: Filter user accounts by role and cohort
     When I set the following fields to these values:
       | courserole_rl | Student |
@@ -51,7 +50,6 @@ Feature: An administrator can filter user accounts by role, cohort and other pro
     And I should see "User Three"
     And I should see "User Four"
 
-  @javascript
   Scenario: Filter user accounts by confirm and authentication method
     When I set the following fields to these values:
       | Confirmed | No |
index 25762a1..8bb57ac 100644 (file)
@@ -100,6 +100,7 @@ class manager {
             'password' => $CFG->messageinbound_hostpass,
             'hostspec' => $CFG->messageinbound_host,
             'secure'   => $CFG->messageinbound_hostssl,
+            'debug'    => empty($CFG->debugimap) ? null : fopen('php://stderr', 'w'),
         );
 
         $this->client = new \Horde_Imap_Client_Socket($configuration);
index 24dbea6..01c7619 100644 (file)
@@ -1,10 +1,9 @@
-@javascript @tool @tool_monitor
+@tool @tool_monitor
 Feature: Enable/disable managment of the event monitor
   In order to manage event monitoring
   As an admin
   I need to enable/disable it
 
-  @javascript
   Scenario: Tool is disabled by default.
     Given I log in as "admin"
     When I navigate to "Event monitoring rules" node in "Site administration > Reports"
index 6490a7f..11ddba2 100644 (file)
@@ -302,7 +302,7 @@ class auth_plugin_mnet extends auth_plugin_base {
             $remoteuser->firstaccess = 0;
             $remoteuser->confirmed = 1;
 
-            $remoteuser->id = $DB->insert_record('user', $remoteuser);
+            $remoteuser->id = user_create_user($remoteuser, false);
             $firsttime = true;
             $localuser = $remoteuser;
         }
diff --git a/auth/mnet/upgrade.txt b/auth/mnet/upgrade.txt
new file mode 100644 (file)
index 0000000..0a3128e
--- /dev/null
@@ -0,0 +1,6 @@
+This files describes API changes in auth_mnet code.
+
+=== 3.1 ===
+
+* Users now are created through user_create_user function which, apart from inserting the user in the database and generating
+  a user_created event, is applying the site default preferences if they are not set.
index c777c86..8f5dc51 100644 (file)
@@ -44,12 +44,11 @@ Feature: Access visible and hidden cohorts
       | user    | course | role           |
       | teacher | C1     | editingteacher |
 
-  @javascript
   Scenario: Teacher can see visible cohorts defined in the above contexts
     When I log in as "teacher"
     And I follow "Course 1"
     And I navigate to "Enrolment methods" node in "Course administration > Users"
-    And I set the field "Add method" to "Cohort sync"
+    And I select "Cohort sync" from the "Add method" singleselect
     Then the "Cohort" select box should contain "Cohort in category 1"
     And the "Cohort" select box should contain "System cohort"
     And the "Cohort" select box should not contain "Cohort hidden in category 1"
@@ -71,13 +70,12 @@ Feature: Access visible and hidden cohorts
     And the "Select members from cohort" select box should not contain "Cohort empty in category 1"
     And the "Select members from cohort" select box should not contain "System empty cohort"
 
-  @javascript
   Scenario: System manager can see all cohorts defined in the above contexts
     When I log in as "user1"
     And I am on site homepage
     And I follow "Course 1"
     And I navigate to "Enrolment methods" node in "Course administration > Users"
-    And I set the field "Add method" to "Cohort sync"
+    And I select "Cohort sync" from the "Add method" singleselect
     Then the "Cohort" select box should contain "Cohort in category 1"
     And the "Cohort" select box should contain "System cohort"
     And the "Cohort" select box should contain "Cohort hidden in category 1"
@@ -99,13 +97,12 @@ Feature: Access visible and hidden cohorts
     And the "Select members from cohort" select box should not contain "Cohort empty in category 1"
     And the "Select members from cohort" select box should not contain "System empty cohort"
 
-  @javascript
   Scenario: Category manager can see all cohorts defined in his category and visible cohorts defined above
     When I log in as "user2"
     And I am on site homepage
     And I follow "Course 1"
     And I navigate to "Enrolment methods" node in "Course administration > Users"
-    And I set the field "Add method" to "Cohort sync"
+    And I select "Cohort sync" from the "Add method" singleselect
     Then the "Cohort" select box should contain "Cohort in category 1"
     And the "Cohort" select box should contain "System cohort"
     And the "Cohort" select box should contain "Cohort hidden in category 1"
index e6983b2..7d8ccdb 100644 (file)
@@ -21,7 +21,6 @@ Feature: Add cohorts of users
       | Description | Test cohort description |
     And I press "Save changes"
 
-  @javascript
   Scenario: Add a cohort
     When I follow "Cohorts"
     Then I should see "Test cohort name"
@@ -29,7 +28,6 @@ Feature: Add cohorts of users
     And I should see "Test cohort description"
     And I should see "Created manually"
 
-  @javascript
   Scenario: Add users to a cohort selecting them from the system users list
     When I add "First User (first@example.com)" user to "333" cohort members
     And I add "Second User (second@example.com)" user to "333" cohort members
@@ -39,7 +37,6 @@ Feature: Add cohorts of users
     And the "Current users" select box should contain "Second User (second@example.com)"
     And the "Current users" select box should not contain "Forth User (forth@example.com)"
 
-  @javascript
   Scenario: Add users to a cohort using a bulk user action
     When I follow "Bulk user actions"
     And I set the field "Available" to "Third User"
index e1fd621..6a18092 100644 (file)
@@ -59,29 +59,16 @@ class behat_cohort extends behat_base {
         if (!$this->getSession()->getPage()->find('css', 'input#cohort_search_q')) {
 
             // With JS enabled we should expand a few tree nodes.
-            if ($this->running_javascript()) {
-                $parentnodes = get_string('administrationsite') . ' > ' .
-                    get_string('users', 'admin') . ' > ' .
-                    get_string('accounts', 'admin');
-                $steps = array_merge(
-                    array(
-                        new Given('I am on homepage'),
-                        new Given('I navigate to "' . get_string('cohorts', 'cohort') . '" node in "' . $parentnodes . '"')
-                    ),
-                    $steps
-                );
-
-            } else {
-                // JS disabled.
-                $steps = array_merge(
-                    array(
-                        new Given('I am on homepage'),
-                        new Given('I follow "' . get_string('administrationsite') . '" node'),
-                        new Given('I follow "' . get_string('cohorts', 'cohort') . '"')
-                    ),
-                    $steps
-                );
-            }
+            $parentnodes = get_string('administrationsite') . ' > ' .
+                get_string('users', 'admin') . ' > ' .
+                get_string('accounts', 'admin');
+            $steps = array_merge(
+                array(
+                    new Given('I am on homepage'),
+                    new Given('I navigate to "' . get_string('cohorts', 'cohort') . '" node in "' . $parentnodes . '"')
+                ),
+                $steps
+            );
         }
 
         return $steps;
index b585272..8e7a742 100644 (file)
@@ -27,7 +27,6 @@ Feature: View cohort list
       | user1 | manager | System       |           |
       | user2 | manager | Category     | CAT1      |
 
-  @javascript
   Scenario: Admin can see system cohorts and all cohorts
     When I log in as "admin"
     And I navigate to "Cohorts" node in "Site administration > Users > Accounts"
@@ -40,7 +39,6 @@ Feature: View cohort list
     And I should see "Cohort in category 3"
     And I log out
 
-  @javascript
   Scenario: Manager can see system cohorts and all cohorts
     When I log in as "user1"
     And I navigate to "Cohorts" node in "Site administration > Users > Accounts"
@@ -53,7 +51,6 @@ Feature: View cohort list
     And I should see "Cohort in category 3"
     And I log out
 
-  @javascript
   Scenario: Manager in category can see cohorts in the category
     When I log in as "user2"
     And I follow "Courses"
index cdf3de9..f4167a5 100644 (file)
@@ -563,6 +563,9 @@ $CFG->admin = 'admin';
 // Prevent theme caching
 // $CFG->themedesignermode = true; // NOT FOR PRODUCTION SERVERS!
 //
+// Enable verbose debug information during fetching of email messages from IMAP server.
+// $CFG->debugimap = true;
+//
 // Prevent JS caching
 // $CFG->cachejs = false; // NOT FOR PRODUCTION SERVERS!
 //
index e0c0e4b..3f8de4e 100644 (file)
@@ -837,12 +837,21 @@ function groups_assign_grouping($groupingid, $groupid, $timeadded = null, $inval
     }
     $DB->insert_record('groupings_groups', $assign);
 
+    $courseid = $DB->get_field('groupings', 'courseid', array('id' => $groupingid));
     if ($invalidatecache) {
         // Invalidate the grouping cache for the course
-        $courseid = $DB->get_field('groupings', 'courseid', array('id' => $groupingid));
         cache_helper::invalidate_by_definition('core', 'groupdata', array(), array($courseid));
     }
 
+    // Trigger event.
+    $params = array(
+        'context' => context_course::instance($courseid),
+        'objectid' => $groupingid,
+        'other' => array('groupid' => $groupid)
+    );
+    $event = \core\event\grouping_group_assigned::create($params);
+    $event->trigger();
+
     return true;
 }
 
@@ -858,12 +867,21 @@ function groups_unassign_grouping($groupingid, $groupid, $invalidatecache = true
     global $DB;
     $DB->delete_records('groupings_groups', array('groupingid'=>$groupingid, 'groupid'=>$groupid));
 
+    $courseid = $DB->get_field('groupings', 'courseid', array('id' => $groupingid));
     if ($invalidatecache) {
         // Invalidate the grouping cache for the course
-        $courseid = $DB->get_field('groupings', 'courseid', array('id' => $groupingid));
         cache_helper::invalidate_by_definition('core', 'groupdata', array(), array($courseid));
     }
 
+    // Trigger event.
+    $params = array(
+        'context' => context_course::instance($courseid),
+        'objectid' => $groupingid,
+        'other' => array('groupid' => $groupid)
+    );
+    $event = \core\event\grouping_group_unassigned::create($params);
+    $event->trigger();
+
     return true;
 }
 
index 464ee74..227f500 100644 (file)
@@ -76,6 +76,8 @@ $string['eventgroupmemberremoved'] = 'Group member removed';
 $string['eventgroupupdated'] = 'Group updated';
 $string['eventgroupingcreated'] = 'Grouping created';
 $string['eventgroupingdeleted'] = 'Grouping deleted';
+$string['eventgroupinggroupassigned'] = 'Group assigned to grouping';
+$string['eventgroupinggroupunassigned'] = 'Group unassigned from grouping';
 $string['eventgroupingupdated'] = 'Grouping updated';
 $string['existingmembers'] = 'Existing members: {$a}';
 $string['filtergroups'] = 'Filter groups by:';
index 49d741a..b1e9455 100644 (file)
@@ -64,9 +64,12 @@ $string['incourse'] = 'in course {$a}';
 $string['index'] = 'Index';
 $string['invalidindexerror'] = 'Index directory either contains an invalid index, or nothing at all.';
 $string['ittook'] = 'It took';
+$string['matchingfile'] = 'Matched from file <span class="filename">{$a}</span>';
+$string['matchingfiles'] = 'Matched from files:';
 $string['next'] = 'Next';
 $string['noindexmessage'] = 'Admin: There appears to be no search index. Please';
 $string['noresults'] = 'No results';
+$string['notitle'] = 'No title';
 $string['normalsearch'] = 'Normal search';
 $string['openedon'] = 'opened on';
 $string['optimize'] = 'Optimize';
index d024ddb..76fd2b9 100644 (file)
Binary files a/lib/amd/build/form-autocomplete.min.js and b/lib/amd/build/form-autocomplete.min.js differ
index 474708e..ba27f86 100644 (file)
@@ -111,6 +111,9 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
         }).fail(notification.exception);
         // Because this function get's called after changing the selection, this is a good place
         // to trigger a change notification.
+        if (typeof M.core_formchangechecker != 'undefined') {
+            M.core_formchangechecker.set_form_changed();
+        }
         originalSelect.change();
     };
 
index 880d061..20c3a6b 100644 (file)
@@ -76,7 +76,12 @@ class behat_form_select extends behat_form_field {
             $browser = \Moodle\BehatExtension\Driver\MoodleSelenium2Driver::getBrowser();
             if (!$singleselect && ($browser == 'phantomjs')) {
                 $script = "Syn.trigger('change', {}, {{ELEMENT}})";
-                $this->session->getDriver()->triggerSynScript($this->field->getXpath(), $script);
+                try {
+                    $this->session->getDriver()->triggerSynScript($this->field->getXpath(), $script);
+                } catch (Exception $e) {
+                    // No need to do anything if element has been removed by JS.
+                    // This is possible when inline editing element is used.
+                }
             }
             $this->session->wait(behat_base::TIMEOUT * 1000, behat_base::PAGE_READY_JS);
         }
diff --git a/lib/classes/event/grouping_group_assigned.php b/lib/classes/event/grouping_group_assigned.php
new file mode 100644 (file)
index 0000000..5deb9bd
--- /dev/null
@@ -0,0 +1,98 @@
+<?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 assigned to grouping event.
+ *
+ * @package    core
+ * @copyright  2016 Vadim Dvorovenko
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core\event;
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Group assigned to grouping event class.
+ *
+ * @package    core
+ * @since      Moodle 3.1
+ * @copyright  2016 Vadim Dvorovenko
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class grouping_group_assigned extends base {
+
+    /**
+     * Returns description of what happened.
+     *
+     * @return string
+     */
+    public function get_description() {
+        return "The user with id '$this->userid' assigned the group with id '{$this->other['groupid']}'" .
+                " to the grouping with id '$this->objectid'.";
+    }
+
+    /**
+     * Return localised event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('eventgroupinggroupassigned', 'group');
+    }
+
+    /**
+     * Get URL related to the action.
+     *
+     * @return \moodle_url
+     */
+    public function get_url() {
+        return new \moodle_url('/group/assign.php', array('id' => $this->objectid));
+    }
+
+    /**
+     * Init method.
+     *
+     * @return void
+     */
+    protected function init() {
+        $this->data['crud'] = 'c';
+        $this->data['edulevel'] = self::LEVEL_OTHER;
+        $this->data['objecttable'] = 'groupings';
+    }
+
+    /**
+     * This is used when restoring course logs where it is required that we
+     * map the objectid to its new value in the new course.
+     *
+     * @return string the name of the restore mapping the objectid links to
+     */
+    public static function get_objectid_mapping() {
+        return array('db' => 'groupings', 'restore' => 'group');
+    }
+
+    /**
+     * This is used when restoring course logs where it is required that we
+     * map the information in 'other' to its new value in the new course.
+     *
+     * @return array an array of other values and their corresponding mapping
+     */
+    public static function get_other_mapping() {
+        $othermapped = array();
+        $othermapped['groupid'] = array('db' => 'groups', 'restore' => 'group');
+        return $othermapped;
+    }
+}
diff --git a/lib/classes/event/grouping_group_unassigned.php b/lib/classes/event/grouping_group_unassigned.php
new file mode 100644 (file)
index 0000000..6fe209e
--- /dev/null
@@ -0,0 +1,98 @@
+<?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 unassigned from grouping event.
+ *
+ * @package    core
+ * @copyright  2016 Vadim Dvorovenko
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core\event;
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Group unassigned from grouping event class.
+ *
+ * @package    core
+ * @since      Moodle 3.1
+ * @copyright  2016 Vadim Dvorovenko
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class grouping_group_unassigned extends base {
+
+    /**
+     * Returns description of what happened.
+     *
+     * @return string
+     */
+    public function get_description() {
+        return "The user with id '$this->userid' unassigned the group with id '{$this->other['groupid']}'" .
+                " from the grouping with id '$this->objectid'.";
+    }
+
+    /**
+     * Return localised event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('eventgroupinggroupunassigned', 'group');
+    }
+
+    /**
+     * Get URL related to the action.
+     *
+     * @return \moodle_url
+     */
+    public function get_url() {
+        return new \moodle_url('/group/assign.php', array('id' => $this->objectid));
+    }
+
+    /**
+     * Init method.
+     *
+     * @return void
+     */
+    protected function init() {
+        $this->data['crud'] = 'd';
+        $this->data['edulevel'] = self::LEVEL_OTHER;
+        $this->data['objecttable'] = 'groupings';
+    }
+
+    /**
+     * This is used when restoring course logs where it is required that we
+     * map the objectid to its new value in the new course.
+     *
+     * @return string the name of the restore mapping the objectid links to
+     */
+    public static function get_objectid_mapping() {
+        return array('db' => 'groupings', 'restore' => 'group');
+    }
+
+    /**
+     * This is used when restoring course logs where it is required that we
+     * map the information in 'other' to its new value in the new course.
+     *
+     * @return array an array of other values and their corresponding mapping
+     */
+    public static function get_other_mapping() {
+        $othermapped = array();
+        $othermapped['groupid'] = array('db' => 'groups', 'restore' => 'group');
+        return $othermapped;
+    }
+}
index 6dbdc1e..f5d5948 100644 (file)
@@ -271,10 +271,13 @@ $definitions = array(
         'staticaccelerationsize' => 3
     ),
 
-    // Grade categories. Stored at request level as invalidation is very aggressive.
+    // Grade categories. Stored at session level as invalidation is very aggressive.
     'grade_categories' => array(
-        'mode' => cache_store::MODE_REQUEST,
+        'mode' => cache_store::MODE_SESSION,
         'simplekeys' => true,
+        'invalidationevents' => array(
+            'changesingradecategories',
+        )
     ),
 
     // Store temporary tables information.
index d5ed2a6..4efa43a 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 6d77779..b64c044 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 d15b0a7..3b9fe61 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 0c37c58..5b86018 100644 (file)
@@ -290,6 +290,8 @@ EditorClean.prototype = {
             {regex: /(<[^>]*?style\s*?=\s*?")([^>"]*)(")/gi, replace: function(match, group1, group2, group3) {
                     // Remove MSO-blah, MSO:blah style attributes.
                     group2 = group2.replace(/(?:^|;)[\s]*MSO[-:](?:&[\w]*;|[^;"])*/gi,"");
+                    // Remove backgroud color style.
+                    group2 = group2.replace(/background-color:.*?;/gi,"");
                     return group1 + group2 + group3;
                 }},
             // Get all class attributes so we can work on them.
index 13d15c4..2723f70 100644 (file)
@@ -2691,7 +2691,6 @@ class grade_category extends grade_object {
      * @return void
      */
     public static function clean_record_set() {
-        $cache = cache::make('core', 'grade_categories');
-        $cache->purge();
+        cache_helper::purge_by_event('changesingradecategories');
     }
 }
index ff0a4f7..23aeb8e 100644 (file)
@@ -250,7 +250,7 @@ class course_modinfo {
         $modnames = get_module_types_names($plural);
         $modnamesused = array();
         foreach ($this->get_cms() as $cmid => $mod) {
-            if (isset($modnames[$mod->modname]) && $mod->uservisible) {
+            if (!isset($modnamesused[$mod->modname]) && isset($modnames[$mod->modname]) && $mod->uservisible) {
                 $modnamesused[$mod->modname] = $modnames[$mod->modname];
             }
         }
index b44b279..47de940 100644 (file)
@@ -2459,7 +2459,7 @@ class paging_bar implements renderable {
 
             $pagenum = $this->page + 1;
 
-            if ($pagenum != $displaypage) {
+            if ($pagenum != $lastpage) {
                 $this->nextlink = html_writer::link(new moodle_url($this->baseurl, array($this->pagevar=>$pagenum)), get_string('next'), array('class'=>'next'));
             }
         }
index c20a16c..f866fe0 100644 (file)
@@ -3377,12 +3377,16 @@ EOD;
                                 array('class' => 'iconsmall')
                             ) . $value->title;
                         }
+
                         $al = new action_menu_link_secondary(
                             $value->url,
                             $pix,
                             $value->title,
                             array('class' => 'icon')
                         );
+                        if (!empty($value->titleidentifier)) {
+                            $al->attributes['data-title'] = $value->titleidentifier;
+                        }
                         $am->add($al);
                         break;
                 }
index 6c2cd9c..33466f5 100644 (file)
@@ -443,10 +443,15 @@ class moodle_page {
      * @return context the main context to which this page belongs.
      */
     protected function magic_get_context() {
+        global $CFG;
         if (is_null($this->_context)) {
             if (CLI_SCRIPT or NO_MOODLE_COOKIES) {
                 // Cli scripts work in system context, do not annoy devs with debug info.
                 // Very few scripts do not use cookies, we can safely use system as default context there.
+            } else if (AJAX_SCRIPT && $CFG->debugdeveloper) {
+                // Throw exception inside AJAX script in developer mode, otherwise the debugging message may be missed.
+                throw new coding_exception('$PAGE->context was not set. You may have forgotten '
+                    .'to call require_login() or $PAGE->set_context()');
             } else {
                 debugging('Coding problem: $PAGE->context was not set. You may have forgotten '
                     .'to call require_login() or $PAGE->set_context(). The page may not display '
index b4362d6..7a4bb65 100644 (file)
@@ -39,6 +39,7 @@ if (empty($CFG->messaging)) {
     throw new moodle_exception('disabled', 'core_message');
 }
 
+$PAGE->set_context(null);
 require_sesskey();
 $action = optional_param('action', null, PARAM_ALPHA);
 $response = null;
index 1964312..c7adafc 100644 (file)
@@ -26,6 +26,8 @@ namespace mod_assign\search;
 
 defined('MOODLE_INTERNAL') || die();
 
+require_once($CFG->dirroot . '/mod/assign/locallib.php');
+
 /**
  * Search area for mod_assign activities.
  *
@@ -34,4 +36,32 @@ defined('MOODLE_INTERNAL') || die();
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 class activity extends \core_search\area\base_activity {
+    /**
+     * Returns true if this area uses file indexing.
+     *
+     * @return bool
+     */
+    public function uses_file_indexing() {
+        return true;
+    }
+
+    /**
+     * Add the attached description files.
+     *
+     * @param document $document The current document
+     * @return null
+     */
+    public function attach_files($document) {
+        $fs = get_file_storage();
+
+        $cm = $this->get_cm($this->get_module_name(), $document->get('itemid'), $document->get('courseid'));
+        $context = \context_module::instance($cm->id);
+
+        $files = $fs->get_area_files($context->id, 'mod_assign', ASSIGN_INTROATTACHMENT_FILEAREA, 0,
+                'sortorder DESC, id ASC', false);
+
+        foreach ($files as $file) {
+            $document->add_stored_file($file);
+        }
+    }
 }
diff --git a/mod/assign/tests/search_test.php b/mod/assign/tests/search_test.php
new file mode 100644 (file)
index 0000000..ad312f6
--- /dev/null
@@ -0,0 +1,131 @@
+<?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/>.
+
+/**
+ * Assign search unit tests.
+ *
+ * @package     mod_assign
+ * @category    test
+ * @copyright   2016 Eric Merrill {@link http://www.merrilldigital.com}
+ * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->dirroot . '/search/tests/fixtures/testable_core_search.php');
+require_once($CFG->dirroot . '/mod/assign/locallib.php');
+
+/**
+ * Provides the unit tests for forum search.
+ *
+ * @package     mod_assign
+ * @category    test
+ * @copyright   2016 Eric Merrill {@link http://www.merrilldigital.com}
+ * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class mod_assign_search_testcase extends advanced_testcase {
+
+    /**
+     * @var string Area id
+     */
+    protected $assignareaid = null;
+
+    public function setUp() {
+        $this->resetAfterTest(true);
+        set_config('enableglobalsearch', true);
+
+        $this->assignareaid = \core_search\manager::generate_areaid('mod_assign', 'activity');
+
+        // Set \core_search::instance to the mock_search_engine as we don't require the search engine to be working to test this.
+        $search = testable_core_search::instance();
+    }
+
+    /**
+     * Test for assign file attachments.
+     *
+     * @return void
+     */
+    public function test_attach_files() {
+        global $USER;
+
+        $this->setAdminUser();
+        // Setup test data.
+        $course = $this->getDataGenerator()->create_course();
+
+        $fs = get_file_storage();
+        $usercontext = context_user::instance($USER->id);
+
+        $record = new stdClass();
+        $record->course = $course->id;
+
+        $assign = $this->getDataGenerator()->create_module('assign', $record);
+        $context = context_module::instance($assign->cmid);
+
+        // Attach the main file. We put them in the draft area, create_module will move them.
+        $filerecord = array(
+            'contextid' => $context->id,
+            'component' => 'mod_assign',
+            'filearea'  => ASSIGN_INTROATTACHMENT_FILEAREA,
+            'itemid'    => 0,
+            'filepath'  => '/'
+        );
+
+        // Attach 4 files.
+        for ($i = 1; $i <= 4; $i++) {
+            $filerecord['filename'] = 'myfile'.$i;
+            $fs->create_file_from_string($filerecord, 'Test assign file '.$i);
+        }
+
+        // And a fifth in a sub-folder.
+        $filerecord['filename'] = 'myfile5';
+        $filerecord['filepath'] = '/subfolder/';
+        $fs->create_file_from_string($filerecord, 'Test assign file 5');
+
+        // Returns the instance as long as the area is supported.
+        $searcharea = \core_search\manager::get_search_area($this->assignareaid);
+        $this->assertInstanceOf('\mod_assign\search\activity', $searcharea);
+
+        $recordset = $searcharea->get_recordset_by_timestamp(0);
+        $nrecords = 0;
+        foreach ($recordset as $record) {
+            $doc = $searcharea->get_document($record);
+            $searcharea->attach_files($doc);
+            $files = $doc->get_files();
+
+            // Assign should return all files attached.
+            $this->assertCount(5, $files);
+
+            // We don't know the order, so get all the names, then sort, then check.
+            $filenames = array();
+            foreach ($files as $file) {
+                $filenames[] = $file->get_filename();
+            }
+            sort($filenames);
+
+            for ($i = 1; $i <= 5; $i++) {
+                $this->assertEquals('myfile'.$i, $filenames[($i - 1)]);
+            }
+
+            $nrecords++;
+        }
+
+        // If there would be an error/failure in the foreach above the recordset would be closed on shutdown.
+        $recordset->close();
+        $this->assertEquals(1, $nrecords);
+    }
+
+}
index a85601c..8882776 100644 (file)
@@ -3654,6 +3654,11 @@ function data_get_all_recordids($dataid, $selectdata = '', $params = null) {
  * @return array $recordids   An array of record ids.
  */
 function data_get_advance_search_ids($recordids, $searcharray, $dataid) {
+    // Check to see if we have any record IDs.
+    if (empty($recordids)) {
+        // Send back an empty search.
+        return array();
+    }
     $searchcriteria = array_keys($searcharray);
     // Loop through and reduce the IDs one search criteria at a time.
     foreach ($searchcriteria as $key) {
index f2bd376..1e54d95 100644 (file)
@@ -189,6 +189,9 @@ class data_advanced_search_sql_test extends advanced_testcase {
      * extra parameters. $alias is the field alias used in the sql query and $commaid
      * is a comma seperated string of record IDs.
      *
+     * Test 3.1: This tests that if no recordids are provided (In a situation where a search is done on an empty database)
+     * That an empty array is returned.
+     *
      * Test 4: data_get_advanced_search_sql provides an array which contains an sql string to be used for displaying records
      * to the user when they use the advanced search criteria and the parameters that go with the sql statement. This test
      * takes that information and does a search on the database, returning a record.
@@ -217,6 +220,10 @@ class data_advanced_search_sql_test extends advanced_testcase {
         $newrecordids = data_get_advance_search_ids($recordids, $this->recordsearcharray, $this->recorddata->id);
         $this->assertEquals($this->datarecordset, $newrecordids);
 
+        // Test 3.1
+        $resultrecordids = data_get_advance_search_ids(array(), $this->recordsearcharray, $this->recorddata->id);
+        $this->assertEmpty($resultrecordids);
+
         // Test 4
         $sortorder = 'ORDER BY r.timecreated ASC , r.id ASC';
         $html = data_get_advanced_search_sql('0', $this->recorddata, $newrecordids, '', $sortorder);
index 30c309a..ccf8639 100644 (file)
@@ -44,6 +44,7 @@ $capabilities = array(
         'contextlevel' => CONTEXT_MODULE,
         'archetypes' => array(
             'guest' => CAP_ALLOW,
+            'frontpage' => CAP_ALLOW,
             'student' => CAP_ALLOW,
             'teacher' => CAP_ALLOW,
             'editingteacher' => CAP_ALLOW,
@@ -58,6 +59,7 @@ $capabilities = array(
         'captype' => 'write',
         'contextlevel' => CONTEXT_MODULE,
         'archetypes' => array(
+            'frontpage' => CAP_ALLOW,
             'student' => CAP_ALLOW
         )
     ),
index 6733a34..80a482f 100644 (file)
@@ -37,7 +37,7 @@
 defined('MOODLE_INTERNAL') || die();
 
 function xmldb_feedback_upgrade($oldversion) {
-    global $CFG;
+    global $CFG, $DB;
 
     // Moodle v2.8.0 release upgrade line.
     // Put any upgrade step following this.
@@ -48,5 +48,14 @@ function xmldb_feedback_upgrade($oldversion) {
     // Moodle v3.0.0 release upgrade line.
     // Put any upgrade step following this.
 
+    if ($oldversion < 2016031600) {
+        // Remove labels from all 'captcha' and 'label' items.
+        $DB->execute('UPDATE {feedback_item} SET label = ? WHERE typ = ? OR typ = ?',
+                array('', 'captcha', 'label'));
+
+        // Data savepoint reached.
+        upgrade_mod_savepoint(true, 2016031600, 'feedback');
+    }
+
     return true;
 }
index 083ba26..1339afd 100644 (file)
@@ -208,6 +208,10 @@ function feedback_import_loaded_data(&$data, $feedbackid) {
         $newitem->typ = $typ;
         $newitem->name = trim($item['#']['ITEMTEXT'][0]['#']);
         $newitem->label = trim($item['#']['ITEMLABEL'][0]['#']);
+        if ($typ === 'captcha' || $typ === 'label') {
+            $newitem->label = '';
+            $newitem->name = '';
+        }
         $newitem->options = trim($item['#']['OPTIONS'][0]['#']);
         $newitem->presentation = trim($item['#']['PRESENTATION'][0]['#']);
         //check old types of radio, check, and so on
index 88847ef..66c2431 100644 (file)
@@ -55,7 +55,7 @@ class feedback_item_captcha extends feedback_item_base {
         $this->item->feedback = $feedback->id;
         $this->item->template = 0;
         $this->item->name = get_string('captcha', 'feedback');
-        $this->item->label = get_string('captcha', 'feedback');
+        $this->item->label = '';
         $this->item->presentation = '';
         $this->item->typ = $this->type;
         $this->item->hasvalue = $this->get_hasvalue();
@@ -139,8 +139,10 @@ class feedback_item_captcha extends feedback_item_base {
 
         //print the question and label
         echo '<div class="feedback_item_label_'.$align.'">';
-        echo '('.$item->label.') ';
-        echo format_text($item->name.$requiredmark, true, false, false);
+        if (strval($item->label) !== '') {
+            echo '('. format_string($item->label).') ';
+        }
+        echo format_text($item->name . $requiredmark, FORMAT_HTML, array('noclean' => true, 'para' => false));
         echo '</div>';
 
     }
@@ -192,8 +194,10 @@ class feedback_item_captcha extends feedback_item_base {
 
             //print the question and label
             echo '<div class="feedback_item_label_'.$align.'">';
-            echo '('.$item->label.') ';
-            echo format_text($item->name.$requiredmark, true, false, false);
+            if (strval($item->label) !== '') {
+                echo '('. format_string($item->label).') ';
+            }
+            echo format_text($item->name . $requiredmark, FORMAT_HTML, array('noclean' => true, 'para' => false));
             $inputname = 'name="'.$item->typ.'_'.$item->id.'"';
             echo '<input type="hidden" value="'.$USER->sesskey.'" '.$inputname.' />';
             echo '</div>';
@@ -259,26 +263,19 @@ class feedback_item_captcha extends feedback_item_base {
      * @return void
      */
     public function print_item_show_value($item, $value = '') {
-        global $DB;
+        global $OUTPUT;
 
         $align = right_to_left() ? 'right' : 'left';
 
-        $cmid = 0;
-        $feedbackid = $item->feedback;
-        if ($feedbackid > 0) {
-            $feedback = $DB->get_record('feedback', array('id'=>$feedbackid));
-            if ($cm = get_coursemodule_from_instance("feedback", $feedback->id, $feedback->course)) {
-                $cmid = $cm->id;
-            }
-        }
-
         $requiredmark = '<img class="req" title="'.get_string('requiredelement', 'form').'" alt="'.
             get_string('requiredelement', 'form').'" src="'.$OUTPUT->pix_url('req') .'" />';
 
         //print the question and label
         echo '<div class="feedback_item_label_'.$align.'">';
-        echo '('.$item->label.') ';
-        echo format_text($item->name.$requiredmark, true, false, false);
+        if (strval($item->label) !== '') {
+            echo '('. format_string($item->label).') ';
+        }
+        echo format_text($item->name . $requiredmark, FORMAT_HTML, array('noclean' => true, 'para' => false));
         echo '</div>';
     }
 
index e44ec33..f9b16d8 100644 (file)
@@ -76,7 +76,7 @@ abstract class feedback_item_form extends moodleform {
         $mform->setType('template', PARAM_INT);
 
         $mform->setType('name', PARAM_RAW);
-        $mform->setType('label', PARAM_ALPHANUM);
+        $mform->setType('label', PARAM_NOTAGS);
 
         $mform->addElement('hidden', 'typ', $this->type);
         $mform->setType('typ', PARAM_ALPHA);
index de56497..7583b79 100644 (file)
@@ -142,7 +142,7 @@ class feedback_item_info extends feedback_item_base {
         if (!isset($value->value)) {
             return '';
         }
-        return userdate($value->value);
+        return $item->presentation == 1 ? userdate($value->value) : $value->value;
     }
 
     public function print_analysed($item, $itemnr = '', $groupid = false, $courseid = false) {
@@ -150,7 +150,11 @@ class feedback_item_info extends feedback_item_base {
         $data = $analysed_item->data;
         if (is_array($data)) {
             echo '<tr><th colspan="2" align="left">';
-            echo $itemnr.'&nbsp;('.$item->label.') '.$item->name;
+            echo $itemnr . ' ';
+            if (strval($item->label) !== '') {
+                echo '('. format_string($item->label).') ';
+            }
+            echo format_text($item->name, FORMAT_HTML, array('noclean' => true, 'para' => false));
             echo '</th></tr>';
             $sizeofdata = count($data);
             for ($i = 0; $i < $sizeofdata; $i++) {
@@ -241,12 +245,14 @@ class feedback_item_info extends feedback_item_base {
 
         //print the question and label
         echo '<div class="feedback_item_label_'.$align.'">';
-        echo '('.$item->label.') ';
-        echo format_text($item->name.$requiredmark, true, false, false);
+        if (strval($item->label) !== '') {
+            echo '('. format_string($item->label).') ';
+        }
+        echo format_text($item->name . $requiredmark, FORMAT_HTML, array('noclean' => true, 'para' => false));
         if ($item->dependitem) {
             if ($dependitem = $DB->get_record('feedback_item', array('id'=>$item->dependitem))) {
                 echo ' <span class="feedback_depend">';
-                echo '('.$dependitem->label.'-&gt;'.$item->dependvalue.')';
+                echo '('.format_string($dependitem->label).'-&gt;'.$item->dependvalue.')';
                 echo '</span>';
             }
         }
@@ -330,7 +336,7 @@ class feedback_item_info extends feedback_item_base {
         //print the question and label
         echo '<div class="feedback_item_label_'.$align.'">';
         echo '<span class="'.$highlight.'">';
-            echo format_text($item->name.$requiredmark, true, false, false);
+        echo format_text($item->name . $requiredmark, FORMAT_HTML, array('noclean' => true, 'para' => false));
         echo '</span>';
         echo '</div>';
 
@@ -363,8 +369,10 @@ class feedback_item_info extends feedback_item_base {
 
         //print the question and label
         echo '<div class="feedback_item_label_'.$align.'">';
-            echo '('.$item->label.') ';
-            echo format_text($item->name . $requiredmark, true, false, false);
+        if (strval($item->label) !== '') {
+            echo '('. format_string($item->label).') ';
+        }
+        echo format_text($item->name . $requiredmark, FORMAT_HTML, array('noclean' => true, 'para' => false));
         echo '</div>';
 
         //print the presentation
index b015cd1..3509908 100644 (file)
@@ -35,7 +35,7 @@ class feedback_label_form extends feedback_item_form {
         $mform->setType('required', PARAM_INT);
         $mform->addElement('hidden', 'name', 'label');
         $mform->setType('template', PARAM_ALPHA);
-        $mform->addElement('hidden', 'label', '-');
+        $mform->addElement('hidden', 'label', '');
         $mform->setType('label', PARAM_ALPHA);
 
         $mform->addElement('header', 'general', get_string($this->type, 'feedback'));
index 6b6d449..d4ca8e0 100644 (file)
@@ -184,7 +184,7 @@ class feedback_item_label extends feedback_item_base {
         if ($item->dependitem) {
             if ($dependitem = $DB->get_record('feedback_item', array('id'=>$item->dependitem))) {
                 echo ' <span class="feedback_depend">';
-                echo '('.$dependitem->label.'-&gt;'.$item->dependvalue.')';
+                echo '('.format_string($dependitem->label).'-&gt;'.$item->dependvalue.')';
                 echo '</span>';
             }
         }
index c8df4aa..5835b9b 100644 (file)
@@ -231,7 +231,11 @@ class feedback_item_multichoice extends feedback_item_base {
         if ($analysed_item) {
             $itemname = $analysed_item[1];
             echo '<tr><th colspan="2" align="left">';
-            echo $itemnr.'&nbsp;('.$item->label.') '.$itemname;
+            echo $itemnr . ' ';
+            if (strval($item->label) !== '') {
+                echo '('. format_string($item->label).') ';
+            }
+            echo $itemname;
             echo '</th></tr>';
 
             $analysed_vals = $analysed_item[2];
@@ -320,12 +324,14 @@ class feedback_item_multichoice extends feedback_item_base {
         if ($info->subtype == 'd') {
             echo '<label for="'. $item->typ . '_' . $item->id .'">';
         }
-        echo '('.$item->label.') ';
+        if (strval($item->label) !== '') {
+            echo '('. format_string($item->label).') ';
+        }
         echo format_text($item->name . $requiredmark, FORMAT_HTML, array('noclean' => true, 'para' => false));
         if ($item->dependitem) {
             if ($dependitem = $DB->get_record('feedback_item', array('id'=>$item->dependitem))) {
                 echo ' <span class="feedback_depend">';
-                echo '('.$dependitem->label.'-&gt;'.$item->dependvalue.')';
+                echo '('.format_string($dependitem->label).'-&gt;'.$item->dependvalue.')';
                 echo '</span>';
             }
         }
@@ -425,7 +431,7 @@ class feedback_item_multichoice extends feedback_item_base {
         echo '<div class="feedback_item_label_'.$align.'">';
         if ($info->subtype == 'd') {
             echo '<label for="'. $inputname .'">';
-            echo format_text($item->name.$requiredmark, true, false, false);
+            echo format_text($item->name . $requiredmark, FORMAT_HTML, array('noclean' => true, 'para' => false));
             if ($highlightrequire AND $item->required AND (count($values) == 0 OR $values[0] == '' OR $values[0] == 0)) {
                 echo '<br class="error"><span id="id_error_'.$inputname.'" class="error"> '.get_string('err_required', 'form').
                     '</span><br id="id_error_break_'.$inputname.'" class="error" >';
@@ -536,7 +542,9 @@ class feedback_item_multichoice extends feedback_item_base {
 
         //print the question and label
         echo '<div class="feedback_item_label_'.$align.'">';
-        echo '('.$item->label.') ';
+        if (strval($item->label) !== '') {
+            echo '('. format_string($item->label).') ';
+        }
         echo format_text($item->name . $requiredmark, FORMAT_HTML, array('noclean' => true, 'para' => false));
         echo '</div>';
 
index 79c2078..0eff14e 100644 (file)
@@ -206,7 +206,11 @@ class feedback_item_multichoicerated extends feedback_item_base {
         $analysed_item = $this->get_analysed($item, $groupid, $courseid);
         if ($analysed_item) {
             echo '<tr><th colspan="2" align="left">';
-            echo $itemnr.'&nbsp;('.$item->label.') '.$analysed_item[1];
+            echo $itemnr . ' ';
+            if (strval($item->label) !== '') {
+                echo '('. format_string($item->label).') ';
+            }
+            echo $analysed_item[1];
             echo '</th></tr>';
             $analysed_vals = $analysed_item[2];
             $pixnr = 0;
@@ -305,12 +309,14 @@ class feedback_item_multichoicerated extends feedback_item_base {
         if ($info->subtype == 'd') {
             echo '<label for="'. $item->typ . '_' . $item->id .'">';
         }
-        echo '('.$item->label.') ';
+        if (strval($item->label) !== '') {
+            echo '('. format_string($item->label).') ';
+        }
         echo format_text($item->name . $requiredmark, FORMAT_HTML, array('noclean' => true, 'para' => false));
         if ($item->dependitem) {
             if ($dependitem = $DB->get_record('feedback_item', array('id'=>$item->dependitem))) {
                 echo ' <span class="feedback_depend">';
-                echo '('.$dependitem->label.'-&gt;'.$item->dependvalue.')';
+                echo '('.format_string($dependitem->label).'-&gt;'.$item->dependvalue.')';
                 echo '</span>';
             }
         }
@@ -356,7 +362,7 @@ class feedback_item_multichoicerated extends feedback_item_base {
         echo '<div class="feedback_item_label_'.$align.'">';
         if ($info->subtype == 'd') {
             echo '<label for="'. $inputname .'">';
-            echo format_text($item->name.$requiredmark, true, false, false);
+            echo format_text($item->name . $requiredmark, FORMAT_HTML, array('noclean' => true, 'para' => false));
             if ($highlightrequire AND $item->required AND intval($value) <= 0) {
                 echo '<br class="error"><span id="id_error_'.$inputname.'" class="error"> '.get_string('err_required', 'form').
                     '</span><br id="id_error_break_'.$inputname.'" class="error" >';
@@ -403,8 +409,10 @@ class feedback_item_multichoicerated extends feedback_item_base {
 
         //print the question and label
         echo '<div class="feedback_item_label_'.$align.'">';
-            echo '('.$item->label.') ';
-            echo format_text($item->name . $requiredmark, FORMAT_HTML, array('noclean' => true, 'para' => false));
+        if (strval($item->label) !== '') {
+            echo '('. format_string($item->label).') ';
+        }
+        echo format_text($item->name . $requiredmark, FORMAT_HTML, array('noclean' => true, 'para' => false));
         echo '</div>';
 
         //print the presentation
index fadd246..9bbacea 100644 (file)
@@ -19,21 +19,12 @@ require_once($CFG->dirroot.'/mod/feedback/item/feedback_item_class.php');
 
 class feedback_item_numeric extends feedback_item_base {
     protected $type = "numeric";
-    public $sep_dec, $sep_thous;
     private $commonparams;
     private $item_form;
     private $item;
 
     public function init() {
-        $this->sep_dec = get_string('separator_decimal', 'feedback');
-        if (substr($this->sep_dec, 0, 2) == '[[') {
-            $this->sep_dec = FEEDBACK_DECIMAL;
-        }
 
-        $this->sep_thous = get_string('separator_thousand', 'feedback');
-        if (substr($this->sep_thous, 0, 2) == '[[') {
-            $this->sep_thous = FEEDBACK_THOUSAND;
-        }
     }
 
     public function build_editform($item, $feedback, $cm) {
@@ -58,17 +49,13 @@ class feedback_item_numeric extends feedback_item_base {
 
         $range_from_to = explode('|', $item->presentation);
         if (isset($range_from_to[0]) AND is_numeric($range_from_to[0])) {
-            $range_from = str_replace(FEEDBACK_DECIMAL,
-                                $this->sep_dec,
-                                floatval($range_from_to[0]));
+            $range_from = $this->format_float($range_from_to[0]);
         } else {
             $range_from = '-';
         }
 
         if (isset($range_from_to[1]) AND is_numeric($range_from_to[1])) {
-            $range_to = str_replace(FEEDBACK_DECIMAL,
-                                $this->sep_dec,
-                                floatval($range_from_to[1]));
+            $range_to = $this->format_float($range_from_to[1]);
         } else {
             $range_to = '-';
         }
@@ -152,7 +139,7 @@ class feedback_item_numeric extends feedback_item_base {
                     $counter++;
                 }
             }
-            $avg = $counter > 0 ? $avg / $counter : 0;
+            $avg = $counter > 0 ? $avg / $counter : null;
             $analysed->data = $data;
             $analysed->avg = $avg;
         }
@@ -173,19 +160,23 @@ class feedback_item_numeric extends feedback_item_base {
 
         if (isset($values->data) AND is_array($values->data)) {
             echo '<tr><th colspan="2" align="left">';
-            echo $itemnr.'&nbsp;('.$item->label.') '.$item->name;
+            echo $itemnr . ' ';
+            if (strval($item->label) !== '') {
+                echo '('. format_string($item->label).') ';
+            }
+            echo format_text($item->name, FORMAT_HTML, array('noclean' => true, 'para' => false));
             echo '</th></tr>';
 
             foreach ($values->data as $value) {
                 echo '<tr><td colspan="2" valign="top" align="left">';
-                echo '-&nbsp;&nbsp;'.number_format($value, 2, $this->sep_dec, $this->sep_thous);
+                echo '-&nbsp;&nbsp;'.$this->format_float($value);
                 echo '</td></tr>';
             }
 
             if (isset($values->avg)) {
-                $avg = number_format($values->avg, 2, $this->sep_dec, $this->sep_thous);
+                $avg = format_float($values->avg, 2);
             } else {
-                $avg = number_format(0, 2, $this->sep_dec, $this->sep_thous);
+                $avg = '-';
             }
             echo '<tr><td align="left" colspan="2"><b>';
             echo get_string('average', 'feedback').': '.$avg;
@@ -204,16 +195,23 @@ class feedback_item_numeric extends feedback_item_base {
         $data = $analysed_item->data;
         if (is_array($data)) {
 
-            //mittelwert anzeigen
+            // Export average.
             $worksheet->write_string($row_offset,
                                      2,
                                      get_string('average', 'feedback'),
                                      $xls_formats->value_bold);
 
-            $worksheet->write_number($row_offset + 1,
-                                     2,
-                                     $analysed_item->avg,
-                                     $xls_formats->value_bold);
+            if (isset($analysed_item->avg)) {
+                $worksheet->write_number($row_offset + 1,
+                                         2,
+                                         $analysed_item->avg,
+                                         $xls_formats->value_bold);
+            } else {
+                $worksheet->write_string($row_offset + 1,
+                                         2,
+                                         '',
+                                         $xls_formats->value_bold);
+            }
             $row_offset++;
         }
         $row_offset++;
@@ -241,14 +239,14 @@ class feedback_item_numeric extends feedback_item_base {
         if (isset($range_from_to[0]) AND is_numeric($range_from_to[0])) {
             $range_from = floatval($range_from_to[0]);
         } else {
-            $range_from = 0;
+            $range_from = '-';
         }
 
         //get the max-value
         if (isset($range_from_to[1]) AND is_numeric($range_from_to[1])) {
             $range_to = floatval($range_from_to[1]);
         } else {
-            $range_to = 0;
+            $range_to = '-';
         }
 
         $requiredmark = ($item->required == 1) ? $strrequiredmark : '';
@@ -256,13 +254,15 @@ class feedback_item_numeric extends feedback_item_base {
         $inputname = $item->typ . '_' . $item->id;
         echo '<div class="feedback_item_label_'.$align.'">';
         echo '<label for="'. $inputname .'">';
-        echo '('.$item->label.') ';
-        echo format_text($item->name . $requiredmark, true, false, false);
+        if (strval($item->label) !== '') {
+            echo '('. format_string($item->label).') ';
+        }
+        echo format_text($item->name . $requiredmark, FORMAT_HTML, array('noclean' => true, 'para' => false));
         if ($item->dependitem) {
             $params = array('id'=>$item->dependitem);
             if ($dependitem = $DB->get_record('feedback_item', $params)) {
                 echo ' <span class="feedback_depend">';
-                echo '('.$dependitem->label.'-&gt;'.$item->dependvalue.')';
+                echo '('.format_string($dependitem->label).'-&gt;'.$item->dependvalue.')';
                 echo '</span>';
             }
         }
@@ -270,17 +270,17 @@ class feedback_item_numeric extends feedback_item_base {
         switch(true) {
             case ($range_from === '-' AND is_numeric($range_to)):
                 echo ' ('.get_string('maximal', 'feedback').
-                        ': '.str_replace(FEEDBACK_DECIMAL, $this->sep_dec, $range_to).')';
+                        ': '.$this->format_float($range_to).')';
                 break;
             case (is_numeric($range_from) AND $range_to === '-'):
                 echo ' ('.get_string('minimal', 'feedback').
-                        ': '.str_replace(FEEDBACK_DECIMAL, $this->sep_dec, $range_from).')';
+                        ': '.$this->format_float($range_from).')';
                 break;
             case ($range_from === '-' AND $range_to === '-'):
                 break;
             default:
-                echo ' ('.str_replace(FEEDBACK_DECIMAL, $this->sep_dec, $range_from).
-                        ' - '.str_replace(FEEDBACK_DECIMAL, $this->sep_dec, $range_to).')';
+                echo ' ('.$this->format_float($range_from).
+                        ' - '.$this->format_float($range_to).')';
                 break;
         }
         echo '</span>';
@@ -301,6 +301,22 @@ class feedback_item_numeric extends feedback_item_base {
         echo '</div>';
     }
 
+    /**
+     * Prints the float nicely in the localized format
+     *
+     * Similar to format_float() but automatically calculates the number of decimal places
+     *
+     * @param float $value The float to print
+     * @return string
+     */
+    protected function format_float($value) {
+        if (!is_numeric($value)) {
+            return null;
+        }
+        $decimal = is_int($value) ? 0 : strlen(substr(strrchr($value, '.'), 1));
+        return format_float($value, $decimal);
+    }
+
     /**
      * print the item at the complete-page of feedback
      *
@@ -323,14 +339,14 @@ class feedback_item_numeric extends feedback_item_base {
         if (isset($range_from_to[0]) AND is_numeric($range_from_to[0])) {
             $range_from = floatval($range_from_to[0]);
         } else {
-            $range_from = 0;
+            $range_from = '-';
         }
 
         //get the max-value
         if (isset($range_from_to[1]) AND is_numeric($range_from_to[1])) {
             $range_to = floatval($range_from_to[1]);
         } else {
-            $range_to = 0;
+            $range_to = '-';
         }
 
         $requiredmark = ($item->required == 1) ? $strrequiredmark : '';
@@ -339,22 +355,22 @@ class feedback_item_numeric extends feedback_item_base {
         $inputname = $item->typ . '_' . $item->id;
         echo '<div class="feedback_item_label_'.$align.'">';
         echo '<label for="'. $inputname .'">';
-        echo format_text($item->name . $requiredmark, true, false, false);
+        echo format_text($item->name . $requiredmark, FORMAT_HTML, array('noclean' => true, 'para' => false));
         echo '<span class="feedback_item_numinfo">';
         switch(true) {
             case ($range_from === '-' AND is_numeric($range_to)):
                 echo ' ('.get_string('maximal', 'feedback').
-                        ': '.str_replace(FEEDBACK_DECIMAL, $this->sep_dec, $range_to).')';
+                        ': '.$this->format_float($range_to).')';
                 break;
             case (is_numeric($range_from) AND $range_to === '-'):
                 echo ' ('.get_string('minimal', 'feedback').
-                        ': '.str_replace(FEEDBACK_DECIMAL, $this->sep_dec, $range_from).')';
+                        ': '.$this->format_float($range_from).')';
                 break;
             case ($range_from === '-' AND $range_to === '-'):
                 break;
             default:
-                echo ' ('.str_replace(FEEDBACK_DECIMAL, $this->sep_dec, $range_from).
-                        ' - '.str_replace(FEEDBACK_DECIMAL, $this->sep_dec, $range_to).')';
+                echo ' ('.$this->format_float($range_from).
+                        ' - '.$this->format_float($range_to).')';
                 break;
         }
         echo '</span>';
@@ -399,34 +415,36 @@ class feedback_item_numeric extends feedback_item_base {
         if (isset($range_from_to[0]) AND is_numeric($range_from_to[0])) {
             $range_from = floatval($range_from_to[0]);
         } else {
-            $range_from = 0;
+            $range_from = '-';
         }
         //get the max-value
         if (isset($range_from_to[1]) AND is_numeric($range_from_to[1])) {
             $range_to = floatval($range_from_to[1]);
         } else {
-            $range_to = 0;
+            $range_to = '-';
         }
         $requiredmark = ($item->required == 1) ? $strrequiredmark : '';
 
         //print the question and label
         echo '<div class="feedback_item_label_'.$align.'">';
-        echo '('.$item->label.') ';
-        echo format_text($item->name . $requiredmark, true, false, false);
+        if (strval($item->label) !== '') {
+            echo '('. format_string($item->label).') ';
+        }
+        echo format_text($item->name . $requiredmark, FORMAT_HTML, array('noclean' => true, 'para' => false));
         switch(true) {
             case ($range_from === '-' AND is_numeric($range_to)):
                 echo ' ('.get_string('maximal', 'feedback').
-                    ': '.str_replace(FEEDBACK_DECIMAL, $this->sep_dec, $range_to).')';
+                    ': '.$this->format_float($range_to).')';
                 break;
             case (is_numeric($range_from) AND $range_to === '-'):
                 echo ' ('.get_string('minimal', 'feedback').
-                    ': '.str_replace(FEEDBACK_DECIMAL, $this->sep_dec, $range_from).')';
+                    ': '.$this->format_float($range_from).')';
                 break;
             case ($range_from === '-' AND $range_to === '-'):
                 break;
             default:
-                echo ' ('.str_replace(FEEDBACK_DECIMAL, $this->sep_dec, $range_from).
-                    ' - '.str_replace(FEEDBACK_DECIMAL, $this->sep_dec, $range_to).')';
+                echo ' ('.$this->format_float($range_from).
+                    ' - '.$this->format_float($range_to).')';
                 break;
         }
         echo '</div>';
@@ -435,7 +453,7 @@ class feedback_item_numeric extends feedback_item_base {
         echo '<div class="feedback_item_presentation_'.$align.'">';
         echo $OUTPUT->box_start('generalbox boxalign'.$align);
         if (is_numeric($value)) {
-            $str_num_value = number_format($value, 2, $this->sep_dec, $this->sep_thous);
+            $str_num_value = $this->format_float($value);
         } else {
             $str_num_value = '&nbsp;';
         }
@@ -445,7 +463,7 @@ class feedback_item_numeric extends feedback_item_base {
     }
 
     public function check_value($value, $item) {
-        $value = str_replace($this->sep_dec, FEEDBACK_DECIMAL, $value);
+        $value = unformat_float($value, true);
         //if the item is not required, so the check is true if no value is given
         if ((!isset($value) OR $value == '') AND $item->required != 1) {
             return true;
@@ -491,7 +509,7 @@ class feedback_item_numeric extends feedback_item_base {
     }
 
     public function create_value($data) {
-        $data = str_replace($this->sep_dec, FEEDBACK_DECIMAL, $data);
+        $data = unformat_float($data, true);
 
         if (is_numeric($data)) {
             $data = floatval($data);
@@ -512,14 +530,14 @@ class feedback_item_numeric extends feedback_item_base {
     }
 
     public function get_presentation($data) {
-        $num1 = str_replace($this->sep_dec, FEEDBACK_DECIMAL, $data->numericrangefrom);
+        $num1 = unformat_float($data->numericrangefrom, true);
         if (is_numeric($num1)) {
             $num1 = floatval($num1);
         } else {
             $num1 = '-';
         }
 
-        $num2 = str_replace($this->sep_dec, FEEDBACK_DECIMAL, $data->numericrangeto);
+        $num2 = unformat_float($data->numericrangeto, true);
         if (is_numeric($num2)) {
             $num2 = floatval($num2);
         } else {
@@ -546,11 +564,11 @@ class feedback_item_numeric extends feedback_item_base {
     }
 
     public function value_type() {
-        return PARAM_FLOAT;
+        return PARAM_TEXT;
     }
 
     public function clean_input_value($value) {
-        $value = str_replace($this->sep_dec, FEEDBACK_DECIMAL, $value);
+        $value = unformat_float($value, true);
         if (!is_numeric($value)) {
             if ($value == '') {
                 return null; //an empty string should be null
@@ -558,6 +576,6 @@ class feedback_item_numeric extends feedback_item_base {
                 return clean_param($value, PARAM_TEXT); //we have to know the value if it is wrong
             }
         }
-        return clean_param($value, $this->value_type());
+        return clean_param($value, PARAM_FLOAT);
     }
 }
index 449d1b7..67ca019 100644 (file)
@@ -44,13 +44,13 @@ class feedback_numeric_form extends feedback_item_form {
                             'rangefrom',
                             get_string('numeric_range_from', 'feedback'),
                             array('size'=>10, 'maxlength'=>10));
-        $mform->setType('rangefrom', PARAM_INT);
+        $mform->setType('rangefrom', PARAM_RAW);
 
         $mform->addElement('text',
                             'rangeto',
                             get_string('numeric_range_to', 'feedback'),
                             array('size'=>10, 'maxlength'=>10));
-        $mform->setType('rangeto', PARAM_INT);
+        $mform->setType('rangeto', PARAM_RAW);
 
         parent::definition();
         $this->set_data($item);
@@ -62,19 +62,13 @@ class feedback_numeric_form extends feedback_item_form {
             return false;
         }
 
-        $itemobj = new feedback_item_numeric();
-
-        $num1 = str_replace($itemobj->sep_dec, FEEDBACK_DECIMAL, $item->rangefrom);
-        if (is_numeric($num1)) {
-            $num1 = floatval($num1);
-        } else {
+        $num1 = unformat_float($item->rangefrom, true);
+        if ($num1 === false || $num1 === null) {
             $num1 = '-';
         }
 
-        $num2 = str_replace($itemobj->sep_dec, FEEDBACK_DECIMAL, $item->rangeto);
-        if (is_numeric($num2)) {
-            $num2 = floatval($num2);
-        } else {
+        $num2 = unformat_float($item->rangeto, true);
+        if ($num2 === false || $num2 === null) {
             $num2 = '-';
         }
 
index 66d76ff..bfb2f0c 100644 (file)
@@ -151,7 +151,11 @@ class feedback_item_textarea extends feedback_item_base {
         $values = feedback_get_group_values($item, $groupid, $courseid);
         if ($values) {
             echo '<tr><th colspan="2" align="left">';
-            echo $itemnr.'&nbsp;('.$item->label.') '.$item->name;
+            echo $itemnr . ' ';
+            if (strval($item->label) !== '') {
+                echo '('. format_string($item->label).') ';
+            }
+            echo format_text($item->name, FORMAT_HTML, array('noclean' => true, 'para' => false));
             echo '</th></tr>';
             foreach ($values as $value) {
                 echo '<tr>';
@@ -208,12 +212,14 @@ class feedback_item_textarea extends feedback_item_base {
         $inputname = $item->typ . '_' . $item->id;
         echo '<div class="feedback_item_label_'.$align.'">';
         echo '<label for="'. $inputname .'">';
-        echo '('.$item->label.') ';
-        echo format_text($item->name.$requiredmark, true, false, false);
+        if (strval($item->label) !== '') {
+            echo '('. format_string($item->label).') ';
+        }
+        echo format_text($item->name . $requiredmark, FORMAT_HTML, array('noclean' => true, 'para' => false));
         if ($item->dependitem) {
             if ($dependitem = $DB->get_record('feedback_item', array('id'=>$item->dependitem))) {
                 echo ' <span class="feedback_depend">';
-                echo '('.$dependitem->label.'-&gt;'.$item->dependvalue.')';
+                echo '('.format_string($dependitem->label).'-&gt;'.$item->dependvalue.')';
                 echo '</span>';
             }
         }
@@ -254,7 +260,7 @@ class feedback_item_textarea extends feedback_item_base {
         $inputname = $item->typ . '_' . $item->id;
         echo '<div class="feedback_item_label_'.$align.'">';
         echo '<label for="'. $inputname .'">';
-            echo format_text($item->name . $requiredmark, true, false, false);
+        echo format_text($item->name . $requiredmark, FORMAT_HTML, array('noclean' => true, 'para' => false));
         if ($highlightrequire AND $item->required AND strval($value) == '') {
             echo '<br class="error"><span id="id_error_'.$inputname.'" class="error"> '.get_string('err_required', 'form').
                 '</span><br id="id_error_break_'.$inputname.'" class="error" >';
@@ -294,8 +300,10 @@ class feedback_item_textarea extends feedback_item_base {
 
         //print the question and label
         echo '<div class="feedback_item_label_'.$align.'">';
-            echo '('.$item->label.') ';
-            echo format_text($item->name . $requiredmark, true, false, false);
+        if (strval($item->label) !== '') {
+            echo '('. format_string($item->label).') ';
+        }
+        echo format_text($item->name . $requiredmark, FORMAT_HTML, array('noclean' => true, 'para' => false));
         echo '</div>';
 
         //print the presentation
index 77027c1..bcc1d89 100644 (file)
@@ -55,7 +55,7 @@ class feedback_item_textfield extends feedback_item_base {
             $itemsize = 30;
         }
 
-        $itemlength = isset($size_and_length[1]) ? $size_and_length[1] : 5;
+        $itemlength = isset($size_and_length[1]) ? $size_and_length[1] : 255;
 
         $item->itemsize = $itemsize;
         $item->itemmaxlength = $itemlength;
@@ -147,7 +147,11 @@ class feedback_item_textfield extends feedback_item_base {
         $values = feedback_get_group_values($item, $groupid, $courseid);
         if ($values) {
             echo '<tr><th colspan="2" align="left">';
-            echo $itemnr.'&nbsp;('.$item->label.') '.$item->name;
+            echo $itemnr . ' ';
+            if (strval($item->label) !== '') {
+                echo '('. format_string($item->label).') ';
+            }
+            echo format_text($item->name, FORMAT_HTML, array('noclean' => true, 'para' => false));
             echo '</th></tr>';
             foreach ($values as $value) {
                 echo '<tr><td colspan="2" valign="top" align="left">';
@@ -198,12 +202,14 @@ class feedback_item_textfield extends feedback_item_base {
         $inputname = $item->typ . '_' . $item->id;
         echo '<div class="feedback_item_label_'.$align.'">';
         echo '<label for="'. $inputname .'">';
-        echo '('.$item->label.') ';
-        echo format_text($item->name.$requiredmark, true, false, false);
+        if (strval($item->label) !== '') {
+            echo '('. format_string($item->label).') ';
+        }
+        echo format_text($item->name . $requiredmark, FORMAT_HTML, array('noclean' => true, 'para' => false));
         if ($item->dependitem) {
             if ($dependitem = $DB->get_record('feedback_item', array('id'=>$item->dependitem))) {
                 echo ' <span class="feedback_depend">';
-                echo '('.$dependitem->label.'-&gt;'.$item->dependvalue.')';
+                echo '('.format_string($dependitem->label).'-&gt;'.$item->dependvalue.')';
                 echo '</span>';
             }
         }
@@ -245,7 +251,7 @@ class feedback_item_textfield extends feedback_item_base {
         $inputname = $item->typ . '_' . $item->id;
         echo '<div class="feedback_item_label_'.$align.'">';
         echo '<label for="'. $inputname .'">';
-            echo format_text($item->name.$requiredmark, true, false, false);
+        echo format_text($item->name . $requiredmark, FORMAT_HTML, array('noclean' => true, 'para' => false));
         if ($highlightrequire AND $item->required AND strval($value) == '') {
             echo '<br class="error"><span id="id_error_'.$inputname.'" class="error"> '.get_string('err_required', 'form').
                 '</span><br id="id_error_break_'.$inputname.'" class="error" >';
@@ -285,8 +291,10 @@ class feedback_item_textfield extends feedback_item_base {
 
         //print the question and label
         echo '<div class="feedback_item_label_'.$align.'">';
-            echo '('.$item->label.') ';
-            echo format_text($item->name . $requiredmark, true, false, false);
+        if (strval($item->label) !== '') {
+            echo '('. format_string($item->label).') ';
+        }
+        echo format_text($item->name . $requiredmark, FORMAT_HTML, array('noclean' => true, 'para' => false));
         echo '</div>';
         echo $OUTPUT->box_start('generalbox boxalign'.$align);
         echo $value ? $value : '&nbsp;';
index 9361dff..da64f61 100644 (file)
@@ -44,10 +44,10 @@ class feedback_textfield_form extends feedback_item_form {
                             get_string('textfield_size', 'feedback').'&nbsp;',
                             array_slice(range(0, 255), 5, 255, true));
 
-        $mform->addElement('select',
+        $mform->addElement('text',
                             'itemmaxlength',
-                            get_string('textfield_maxlength', 'feedback').'&nbsp;',
-                            array_slice(range(0, 255), 5, 255, true));
+                            get_string('textfield_maxlength', 'feedback'));
+        $mform->setType('itemmaxlength', PARAM_INT);
 
         parent::definition();
         $this->set_data($item);
index c05b93a..79fb95f 100644 (file)
@@ -156,7 +156,8 @@ $string['mapcourses_help'] = 'Once you have selected the relevant course(s) from
 you can associate them with this feedback using map course(s). Multiple courses may be selected by holding down the Apple or Ctrl key whilst clicking on the course names. A course may be disassociated from a feedback at any time.';
 $string['mappedcourses'] = 'Mapped courses';
 $string['max_args_exceeded'] = 'Max 6 arguments can be handled, too many arguments for';
-$string['maximal'] = 'maximal';
+$string['minimal'] = 'minimum';
+$string['maximal'] = 'maximum';
 $string['messageprovider:message'] = 'Feedback reminder';
 $string['messageprovider:submission'] = 'Feedback notifications';
 $string['mode'] = 'Mode';
index f28d769..df09db1 100644 (file)
@@ -1450,7 +1450,9 @@ function feedback_get_depend_candidates_for_item($feedback, $item) {
     }
     //adding the choose-option
     foreach ($feedbackitems as $key => $val) {
-        $dependitems[$key] = $val;
+        if (trim(strval($val)) !== '') {
+            $dependitems[$key] = format_string($val);
+        }
     }
     return $dependitems;
 }
index d6dcbd6..0f506cc 100644 (file)
@@ -62,6 +62,7 @@ Feature: Test creating different types of feedback questions
     And I add a "Numeric answer" question to the feedback with:
       | Question               | this is a numeric answer |
       | Label                  | numeric                  |
+      | Range from             | 0                        |
       | Range to               | 100                      |
     And I add a "Short text answer" question to the feedback with:
       | Question               | this is a short text answer |
@@ -124,8 +125,8 @@ Feature: Test creating different types of feedback questions
     And I should see "1 (50.00 %)" in the "option l (1):" "table_row"
     And I should see "1 (50.00 %)" in the "option m (5):" "table_row"
     And I should see "Average: 3.00" in the "(multichoice4)" "table"
-    And I should see "35.00" in the "(numeric)" "table"
-    And I should see "71.00" in the "(numeric)" "table"
+    And I should see "35" in the "(numeric)" "table"
+    And I should see "71" in the "(numeric)" "table"
     And I should see "Average: 53.00" in the "(numeric)" "table"
     And I should see "no way" in the "(shorttext)" "table"
     And I should see "hello" in the "(shorttext)" "table"
index 567bc93..7abfd7a 100644 (file)
@@ -24,7 +24,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2015111600;       // The current module version (Date: YYYYMMDDXX)
+$plugin->version   = 2016031600;       // The current module version (Date: YYYYMMDDXX)
 $plugin->requires  = 2015111000;    // Requires this Moodle version
 $plugin->component = 'mod_feedback';   // Full name of the plugin (used for diagnostics)
 $plugin->cron      = 0;
index 160dc93..0cf3218 100644 (file)
@@ -34,4 +34,31 @@ defined('MOODLE_INTERNAL') || die();
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 class activity extends \core_search\area\base_activity {
+    /**
+     * Returns true if this area uses file indexing.
+     *
+     * @return bool
+     */
+    public function uses_file_indexing() {
+        return true;
+    }
+
+    /**
+     * Add all the folder files to the index.
+     *
+     * @param document $document The current document
+     * @return null
+     */
+    public function attach_files($document) {
+        $fs = get_file_storage();
+
+        $cm = $this->get_cm($this->get_module_name(), $document->get('itemid'), $document->get('courseid'));
+        $context = \context_module::instance($cm->id);
+
+        $files = $fs->get_area_files($context->id, 'mod_folder', 'content', 0, 'sortorder DESC, id ASC', false);
+
+        foreach ($files as $file) {
+            $document->add_stored_file($file);
+        }
+    }
 }
diff --git a/mod/folder/tests/search_test.php b/mod/folder/tests/search_test.php
new file mode 100644 (file)
index 0000000..0c9f751
--- /dev/null
@@ -0,0 +1,130 @@
+<?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/>.
+
+/**
+ * Folder search unit tests.
+ *
+ * @package     mod_folder
+ * @category    test
+ * @copyright   2016 Eric Merrill {@link http://www.merrilldigital.com}
+ * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->dirroot . '/search/tests/fixtures/testable_core_search.php');
+
+/**
+ * Provides the unit tests for forum search.
+ *
+ * @package     mod_folder
+ * @category    test
+ * @copyright   2016 Eric Merrill {@link http://www.merrilldigital.com}
+ * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class mod_folder_search_testcase extends advanced_testcase {
+
+    /**
+     * @var string Area id
+     */
+    protected $folderareaid = null;
+
+    public function setUp() {
+        $this->resetAfterTest(true);
+        set_config('enableglobalsearch', true);
+
+        $this->folderareaid = \core_search\manager::generate_areaid('mod_folder', 'activity');
+
+        // Set \core_search::instance to the mock_search_engine as we don't require the search engine to be working to test this.
+        $search = testable_core_search::instance();
+    }
+
+    /**
+     * Test for folder file attachments.
+     *
+     * @return void
+     */
+    public function test_attach_files() {
+        global $USER;
+
+        $this->setAdminUser();
+        // Setup test data.
+        $course = $this->getDataGenerator()->create_course();
+
+        $fs = get_file_storage();
+        $usercontext = context_user::instance($USER->id);
+
+        $record = new stdClass();
+        $record->course = $course->id;
+        $record->files = file_get_unused_draft_itemid();
+
+        // Attach the main file. We put them in the draft area, create_module will move them.
+        $filerecord = array(
+            'contextid' => $usercontext->id,
+            'component' => 'user',
+            'filearea'  => 'draft',
+            'itemid'    => $record->files,
+            'filepath'  => '/'
+        );
+
+        // Attach 4 files.
+        for ($i = 1; $i <= 4; $i++) {
+            $filerecord['filename'] = 'myfile'.$i;
+            $fs->create_file_from_string($filerecord, 'Test folder file '.$i);
+        }
+
+        // And a fifth in a sub-folder.
+        $filerecord['filename'] = 'myfile5';
+        $filerecord['filepath'] = '/subfolder/';
+        $fs->create_file_from_string($filerecord, 'Test folder file 5');
+
+        $this->getDataGenerator()->create_module('folder', $record);
+
+        // Returns the instance as long as the area is supported.
+        $searcharea = \core_search\manager::get_search_area($this->folderareaid);
+        $this->assertInstanceOf('\mod_folder\search\activity', $searcharea);
+
+        $recordset = $searcharea->get_recordset_by_timestamp(0);
+        $nrecords = 0;
+        foreach ($recordset as $record) {
+            $doc = $searcharea->get_document($record);
+            $searcharea->attach_files($doc);
+            $files = $doc->get_files();
+
+            // Folder should return all files attached.
+            $this->assertCount(5, $files);
+
+            // We don't know the order, so get all the names, then sort, then check.
+            $filenames = array();
+            foreach ($files as $file) {
+                $filenames[] = $file->get_filename();
+            }
+            sort($filenames);
+
+            for ($i = 1; $i <= 5; $i++) {
+                $this->assertEquals('myfile'.$i, $filenames[($i - 1)]);
+            }
+
+            $nrecords++;
+        }
+
+        // If there would be an error/failure in the foreach above the recordset would be closed on shutdown.
+        $recordset->close();
+        $this->assertEquals(1, $nrecords);
+    }
+
+}
index 8729aab..5836723 100644 (file)
@@ -65,7 +65,7 @@ class post extends \core_search\area\base_mod {
                   FROM {forum_posts} fp
                   JOIN {forum_discussions} fd ON fd.id = fp.discussion
                   JOIN {forum} f ON f.id = fd.forum
-                WHERE fp.modified >= ? ORDER BY fp.modified ASC';
+                 WHERE fp.modified >= ? ORDER BY fp.modified ASC';
         return $DB->get_recordset_sql($sql, array($modifiedfrom));
     }
 
@@ -73,9 +73,10 @@ class post extends \core_search\area\base_mod {
      * Returns the document associated with this post id.
      *
      * @param stdClass $record Post info.
+     * @param array    $options
      * @return \core_search\document
      */
-    public function get_document($record) {
+    public function get_document($record, $options = array()) {
 
         try {
             $cm = $this->get_cm('forum', $record->forumid, $record->courseid);
@@ -96,15 +97,62 @@ class post extends \core_search\area\base_mod {
         $doc->set('title', $record->subject);
         $doc->set('content', content_to_text($record->message, $record->messageformat));
         $doc->set('contextid', $context->id);
-        $doc->set('type', \core_search\manager::TYPE_TEXT);
         $doc->set('courseid', $record->courseid);
         $doc->set('userid', $record->userid);
         $doc->set('owneruserid', \core_search\manager::NO_OWNER_ID);
         $doc->set('modified', $record->modified);
 
+        // Check if this document should be considered new.
+        if (isset($options['lastindexedtime']) && ($options['lastindexedtime'] < $record->created)) {
+            // If the document was created after the last index time, it must be new.
+            $doc->set_is_new(true);
+        }
+
         return $doc;
     }
 
+    /**
+     * Returns true if this area uses file indexing.
+     *
+     * @return bool
+     */
+    public function uses_file_indexing() {
+        return true;
+    }
+
+    /**
+     * Add the forum post attachments.
+     *
+     * @param document $document The current document
+     * @return null
+     */
+    public function attach_files($document) {
+        global $DB;
+
+        $postid = $document->get('itemid');
+
+        try {
+            $post = $this->get_post($postid);
+        } catch (\dml_missing_record_exception $e) {
+            unset($this->postsdata[$postid]);
+            debugging('Could not get record to attach files to '.$document->get('id'), DEBUG_DEVELOPER);
+            return;
+        }
+
+        // Because this is used during indexing, we don't want to cache posts. Would result in memory leak.
+        unset($this->postsdata[$postid]);
+
+        $cm = $this->get_cm('forum', $post->forum, $document->get('courseid'));
+        $context = \context_module::instance($cm->id);
+
+        // Get the files and attach them.
+        $fs = get_file_storage();
+        $files = $fs->get_area_files($context->id, 'mod_forum', 'attachment', $postid, "filename", false);
+        foreach ($files as $file) {
+            $document->add_stored_file($file);
+        }
+    }
+
     /**
      * Whether the user can access the document or not.
      *
index a1e9069..5aec1ba 100644 (file)
@@ -826,8 +826,8 @@ function forum_cron() {
                 // MS Outlook / Office uses poorly documented and non standard headers, including
                 // Thread-Topic which overrides the Subject and shouldn't contain Re: or Fwd: etc.
                 $a->subject = $discussion->name;
-                $postsubject = html_to_text(get_string('postmailsubject', 'forum', $a), 0);
-                $userfrom->customheaders[] = "Thread-Topic: $postsubject";
+                $threadtopic = html_to_text(get_string('postmailsubject', 'forum', $a), 0);
+                $userfrom->customheaders[] = "Thread-Topic: $threadtopic";
                 $userfrom->customheaders[] = "Thread-Index: " . substr($rootid, 1, 28);
 
                 // Send the post now!
index d979a4f..40f5be4 100644 (file)
@@ -184,6 +184,8 @@ class mod_forum_mail_testcase extends advanced_testcase {
         // Add a post to the discussion.
         $record = new stdClass();
         $record->course = $forum->course;
+        $strre = get_string('re', 'forum');
+        $record->subject = $strre . ' ' . $discussion->subject;
         $record->userid = $author->id;
         $record->forum = $forum->id;
         $record->discussion = $discussion->id;
@@ -834,6 +836,33 @@ class mod_forum_mail_testcase extends advanced_testcase {
         $this->assertEquals($expectedsubject, $message->subject);
     }
 
+    /**
+     * Test inital email and reply email subjects
+     */
+    public function test_subjects() {
+        $this->resetAfterTest(true);
+
+        $course = $this->getDataGenerator()->create_course();
+
+        $options = array('course' => $course->id, 'forcesubscribe' => FORUM_FORCESUBSCRIBE);
+        $forum = $this->getDataGenerator()->create_module('forum', $options);
+
+        list($author) = $this->helper_create_users($course, 1);
+        list($commenter) = $this->helper_create_users($course, 1);
+
+        $strre = get_string('re', 'forum');
+
+        // New posts should not have Re: in the subject.
+        list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
+        $messages = $this->helper_run_cron_check_count($post, 2);
+        $this->assertNotContains($strre, $messages[0]->subject);
+
+        // Replies should have Re: in the subject.
+        $reply = $this->helper_post_to_discussion($forum, $discussion, $commenter);
+        $messages = $this->helper_run_cron_check_count($reply, 2);
+        $this->assertContains($strre, $messages[0]->subject);
+    }
+
     /**
      * dataProvider for test_forum_post_email_templates().
      */
index 6dc5033..c5bc5ab 100644 (file)
@@ -267,4 +267,107 @@ class mod_forum_search_testcase extends advanced_testcase {
         $this->assertEquals(\core_search\manager::ACCESS_GRANTED, $searcharea->check_access($discussion1reply1->id));
         $this->assertEquals(\core_search\manager::ACCESS_DENIED, $searcharea->check_access($discussion2reply1->id));
     }
+
+    /**
+     * Test for post attachments.
+     *
+     * @return void
+     */
+    public function test_attach_files() {
+        global $DB;
+
+        $fs = get_file_storage();
+
+        // Returns the instance as long as the area is supported.
+        $searcharea = \core_search\manager::get_search_area($this->forumpostareaid);
+        $this->assertInstanceOf('\mod_forum\search\post', $searcharea);
+
+        $user1 = self::getDataGenerator()->create_user();
+        $user2 = self::getDataGenerator()->create_user();
+
+        $course1 = self::getDataGenerator()->create_course();
+
+        $this->getDataGenerator()->enrol_user($user1->id, $course1->id, 'student');
+        $this->getDataGenerator()->enrol_user($user2->id, $course1->id, 'student');
+
+        $record = new stdClass();
+        $record->course = $course1->id;
+
+        $forum1 = self::getDataGenerator()->create_module('forum', $record);
+
+        // Create discussion1.
+        $record = new stdClass();
+        $record->course = $course1->id;
+        $record->userid = $user1->id;
+        $record->forum = $forum1->id;
+        $record->message = 'discussion';
+        $record->attachemt = 1;
+        $discussion1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
+
+        // Attach 2 file to the discussion post.
+        $post = $DB->get_record('forum_posts', array('discussion' => $discussion1->id));
+        $filerecord = array(
+            'contextid' => context_module::instance($forum1->cmid)->id,
+            'component' => 'mod_forum',
+            'filearea'  => 'attachment',
+            'itemid'    => $post->id,
+            'filepath'  => '/',
+            'filename'  => 'myfile1'
+        );
+        $file1 = $fs->create_file_from_string($filerecord, 'Some contents 1');
+        $filerecord['filename'] = 'myfile2';
+        $file2 = $fs->create_file_from_string($filerecord, 'Some contents 2');
+
+        // Create post1 in discussion1.
+        $record = new stdClass();
+        $record->discussion = $discussion1->id;
+        $record->parent = $discussion1->firstpost;
+        $record->userid = $user2->id;
+        $record->message = 'post2';
+        $record->attachemt = 1;
+        $discussion1reply1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
+
+        $filerecord['itemid'] = $discussion1reply1->id;
+        $filerecord['filename'] = 'myfile3';
+        $file3 = $fs->create_file_from_string($filerecord, 'Some contents 3');
+
+        // Create post2 in discussion1.
+        $record = new stdClass();
+        $record->discussion = $discussion1->id;
+        $record->parent = $discussion1->firstpost;
+        $record->userid = $user2->id;
+        $record->message = 'post3';
+        $discussion1reply2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
+
+        // Now get all the posts and see if they have the right files attached.
+        $searcharea = \core_search\manager::get_search_area($this->forumpostareaid);
+        $recordset = $searcharea->get_recordset_by_timestamp(0);
+        $nrecords = 0;
+        foreach ($recordset as $record) {
+            $doc = $searcharea->get_document($record);
+            $searcharea->attach_files($doc);
+            $files = $doc->get_files();
+            // Now check that each doc has the right files on it.
+            switch ($doc->get('itemid')) {
+                case ($post->id):
+                    $this->assertCount(2, $files);
+                    $this->assertEquals($file1->get_id(), $files[$file1->get_id()]->get_id());
+                    $this->assertEquals($file2->get_id(), $files[$file2->get_id()]->get_id());
+                    break;
+                case ($discussion1reply1->id):
+                    $this->assertCount(1, $files);
+                    $this->assertEquals($file3->get_id(), $files[$file3->get_id()]->get_id());
+                    break;
+                case ($discussion1reply2->id):
+                    $this->assertCount(0, $files);
+                    break;
+                default:
+                    $this->fail('Unexpected post returned');
+                    break;
+            }
+            $nrecords++;
+        }
+        $recordset->close();
+        $this->assertEquals(3, $nrecords);
+    }
 }
index 32397db..40e223f 100644 (file)
@@ -61,9 +61,10 @@ class entry extends \core_search\area\base_mod {
      * Returns the documents associated with this glossary entry id.
      *
      * @param stdClass $entry glossary entry.
+     * @param array    $options
      * @return \core_search\document
      */
-    public function get_document($entry) {
+    public function get_document($entry, $options = array()) {
         global $DB;
 
         $keywords = array();
@@ -92,12 +93,17 @@ class entry extends \core_search\area\base_mod {
         $doc->set('title', $entry->concept);
         $doc->set('content', content_to_text($entry->definition, $entry->definitionformat));
         $doc->set('contextid', $context->id);
-        $doc->set('type', \core_search\manager::TYPE_TEXT);
         $doc->set('courseid', $entry->course);
         $doc->set('userid', $entry->userid);
         $doc->set('owneruserid', \core_search\manager::NO_OWNER_ID);
         $doc->set('modified', $entry->timemodified);
 
+        // Check if this document should be considered new.
+        if (isset($options['lastindexedtime']) && ($options['lastindexedtime'] < $entry->timecreated)) {
+            // If the document was created after the last index time, it must be new.
+            $doc->set_is_new(true);
+        }
+
         // Adding keywords as extra info.
         if ($keywords) {
             $doc->set('description1', implode(' ' , $keywords));
index d38fe93..c37a393 100644 (file)
@@ -43,9 +43,10 @@ class activity extends \core_search\area\base_activity {
      * description field is not.
      *
      * @param stdClass $record
+     * @param array    $options
      * @return \core_search\document
      */
-    public function get_document($record) {
+    public function get_document($record, $options = array()) {
 
         try {
             $cm = $this->get_cm($this->get_module_name(), $record->id, $record->course);
@@ -66,7 +67,6 @@ class activity extends \core_search\area\base_activity {
         $doc->set('title', $record->name);
         $doc->set('content', content_to_text($record->content, $record->contentformat));
         $doc->set('contextid', $context->id);
-        $doc->set('type', \core_search\manager::TYPE_TEXT);
         $doc->set('courseid', $record->course);
         $doc->set('owneruserid', \core_search\manager::NO_OWNER_ID);
         $doc->set('modified', $record->timemodified);
index a9c4fe2..ba7f3a3 100644 (file)
@@ -708,6 +708,17 @@ class structure {
             $moveafterslotnumber = (int) $this->slots[$idmoveafter]->slot;
         }
 
+        // If the action came in as moving a slot to itself, normalise this to
+        // moving the slot to after the previous slot.
+        if ($moveafterslotnumber == $movingslotnumber) {
+            $moveafterslotnumber = $moveafterslotnumber - 1;
+        }
+
+        $followingslotnumber = $moveafterslotnumber + 1;
+        if ($followingslotnumber == $movingslotnumber) {
+            $followingslotnumber += 1;
+        }
+
         // Check the target page number is OK.
         if ($page == 0) {
             $page = 1;
@@ -716,16 +727,10 @@ class structure {
                 $page < 1) {
             throw new \coding_exception('The target page number is too small.');
         } else if (!$this->is_last_slot_in_quiz($moveafterslotnumber) &&
-                $page > $this->get_page_number_for_slot($moveafterslotnumber + 1)) {
+                $page > $this->get_page_number_for_slot($followingslotnumber)) {
             throw new \coding_exception('The target page number is too large.');
         }
 
-        // If the action came in as moving a slot to itself, normalise this to
-        // moving the slot to after the previosu slot.
-        if ($moveafterslotnumber == $movingslotnumber) {
-            $moveafterslotnumber = $moveafterslotnumber - 1;
-        }
-
         // Work out how things are being moved.
         $slotreorder = array();
         if ($moveafterslotnumber > $movingslotnumber) {
@@ -768,10 +773,12 @@ class structure {
                 $headingmoveafter = $movingslotnumber;
                 $headingmovebefore = $movingslotnumber + 2;
                 $headingmovedirection = -1;
-            } else {
+            } else if ($page < $movingslot->page) {
                 $headingmoveafter = $movingslotnumber - 1;
                 $headingmovebefore = $movingslotnumber + 1;
                 $headingmovedirection = 1;
+            } else {
+                return; // Nothing to do.
             }
         }
 
index c8590e3..d56585e 100644 (file)
@@ -165,5 +165,30 @@ function xmldb_quiz_upgrade($oldversion) {
     // Moodle v3.0.0 release upgrade line.
     // Put any upgrade step following this.
 
+    if ($oldversion < 2016032600) {
+        // Update quiz_sections to repair quizzes what were broken by MDL-53507.
+        $problemquizzes = $DB->get_records_sql("
+                SELECT quizid, MIN(firstslot) AS firstsectionfirstslot
+                FROM {quiz_sections}
+                GROUP BY quizid
+                HAVING MIN(firstslot) > 1");
+
+        if ($problemquizzes) {
+            $pbar = new progress_bar('upgradequizfirstsection', 500, true);
+            $total = count($problemquizzes);
+            $done = 0;
+            foreach ($problemquizzes as $problemquiz) {
+                $DB->set_field('quiz_sections', 'firstslot', 1,
+                        array('quizid' => $problemquiz->quizid,
+                        'firstslot' => $problemquiz->firstsectionfirstslot));
+                $done += 1;
+                $pbar->update($done, $total, "Fixing quiz layouts - {$done}/{$total}.");
+            }
+        }
+
+        // Quiz savepoint reached.
+        upgrade_mod_savepoint(true, 2016032600, 'quiz');
+    }
+
     return true;
 }
index 6210f81..a8e39f8 100644 (file)
@@ -515,7 +515,7 @@ class mod_quiz_structure_testcase extends advanced_testcase {
             ), $structure);
     }
 
-    public function test_move_slot_to_down_start_of_second_section() {
+    public function test_move_slot_down_to_start_of_second_section() {
         $quizobj = $this->create_test_quiz(array(
                 'Heading 1',
                 array('TF1', 1, 'truefalse'),
@@ -539,6 +539,63 @@ class mod_quiz_structure_testcase extends advanced_testcase {
             ), $structure);
     }
 
+    public function test_move_first_slot_down_to_start_of_page_2() {
+        $quizobj = $this->create_test_quiz(array(
+                'Heading 1',
+                array('TF1', 1, 'truefalse'),
+                array('TF2', 2, 'truefalse'),
+            ));
+        $structure = \mod_quiz\structure::create_for_quiz($quizobj);
+
+        $idtomove = $structure->get_question_in_slot(1)->slotid;
+        $structure->move_slot($idtomove, 0, '2');
+
+        $structure = \mod_quiz\structure::create_for_quiz($quizobj);
+        $this->assert_quiz_layout(array(
+                'Heading 1',
+                array('TF1', 1, 'truefalse'),
+                array('TF2', 1, 'truefalse'),
+            ), $structure);
+    }
+
+    public function test_move_first_slot_to_same_place_on_page_1() {
+        $quizobj = $this->create_test_quiz(array(
+                'Heading 1',
+                array('TF1', 1, 'truefalse'),
+                array('TF2', 2, 'truefalse'),
+            ));
+        $structure = \mod_quiz\structure::create_for_quiz($quizobj);
+
+        $idtomove = $structure->get_question_in_slot(1)->slotid;
+        $structure->move_slot($idtomove, 0, '1');
+
+        $structure = \mod_quiz\structure::create_for_quiz($quizobj);
+        $this->assert_quiz_layout(array(
+                'Heading 1',
+                array('TF1', 1, 'truefalse'),
+                array('TF2', 2, 'truefalse'),
+            ), $structure);
+    }
+
+    public function test_move_first_slot_to_before_page_1() {
+        $quizobj = $this->create_test_quiz(array(
+                'Heading 1',
+                array('TF1', 1, 'truefalse'),
+                array('TF2', 2, 'truefalse'),
+            ));
+        $structure = \mod_quiz\structure::create_for_quiz($quizobj);
+
+        $idtomove = $structure->get_question_in_slot(1)->slotid;
+        $structure->move_slot($idtomove, 0, '');
+
+        $structure = \mod_quiz\structure::create_for_quiz($quizobj);
+        $this->assert_quiz_layout(array(
+                'Heading 1',
+                array('TF1', 1, 'truefalse'),
+                array('TF2', 2, 'truefalse'),
+            ), $structure);
+    }
+
     public function test_move_slot_up_to_start_of_second_section() {
         $quizobj = $this->create_test_quiz(array(
                 'Heading 1',
index 3889380..c70290a 100644 (file)
@@ -24,7 +24,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2016032109;
+$plugin->version   = 2016032600;
 $plugin->requires  = 2015111000;
 $plugin->component = 'mod_quiz';
 $plugin->cron      = 60;
index 4f3031c..1b40ca5 100644 (file)
@@ -34,4 +34,34 @@ defined('MOODLE_INTERNAL') || die();
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 class activity extends \core_search\area\base_activity {
+    /**
+     * Returns true if this area uses file indexing.
+     *
+     * @return bool
+     */
+    public function uses_file_indexing() {
+        return true;
+    }
+
+    /**
+     * Add the main file to the index.
+     *
+     * @param document $document The current document
+     * @return null
+     */
+    public function attach_files($document) {
+        $fs = get_file_storage();
+
+        $cm = $this->get_cm($this->get_module_name(), $document->get('itemid'), $document->get('courseid'));
+        $context = \context_module::instance($cm->id);
+
+        // Order by sortorder desc, the first is consided the main file.
+        $files = $fs->get_area_files($context->id, 'mod_resource', 'content', 0, 'sortorder DESC, id ASC', false);
+
+        $mainfile = $files ? reset($files) : null;
+        if ($mainfile && $mainfile->get_sortorder() > 0) {
+            $document->add_stored_file($mainfile);
+        }
+    }
+
 }
diff --git a/mod/resource/tests/search_test.php b/mod/resource/tests/search_test.php
new file mode 100644 (file)
index 0000000..b4b7d09
--- /dev/null
@@ -0,0 +1,116 @@
+<?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/>.
+
+/**
+ * Resource search unit tests.
+ *
+ * @package     mod_resource
+ * @category    test
+ * @copyright   2016 Eric Merrill {@link http://www.merrilldigital.com}
+ * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->dirroot . '/search/tests/fixtures/testable_core_search.php');
+
+/**
+ * Provides the unit tests for forum search.
+ *
+ * @package     mod_resource
+ * @category    test
+ * @copyright   2016 Eric Merrill {@link http://www.merrilldigital.com}
+ * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class mod_resource_search_testcase extends advanced_testcase {
+
+    /**
+     * @var string Area id
+     */
+    protected $resourceareaid = null;
+
+    public function setUp() {
+        $this->resetAfterTest(true);
+        set_config('enableglobalsearch', true);
+
+        $this->resourceareaid = \core_search\manager::generate_areaid('mod_resource', 'activity');
+
+        // Set \core_search::instance to the mock_search_engine as we don't require the search engine to be working to test this.
+        $search = testable_core_search::instance();
+    }
+
+    /**
+     * Test for resource file attachments.
+     *
+     * @return void
+     */
+    public function test_attach_files() {
+        global $USER;
+
+        $this->setAdminUser();
+        // Setup test data.
+        $course = $this->getDataGenerator()->create_course();
+
+        $fs = get_file_storage();
+        $usercontext = context_user::instance($USER->id);
+
+        $record = new stdClass();
+        $record->course = $course->id;
+        $record->files = file_get_unused_draft_itemid();
+
+        // Attach the main file. We put them in the draft area, create_module will move them.
+        $filerecord = array(
+            'contextid' => $usercontext->id,
+            'component' => 'user',
+            'filearea'  => 'draft',
+            'itemid'    => $record->files,
+            'filepath'  => '/',
+            'filename'  => 'mainfile',
+            'sortorder' => 1
+        );
+        $fs->create_file_from_string($filerecord, 'Test resource file');
+
+        // Attach a second file that shouldn't be returned with the search doc.
+        $filerecord['filename'] = 'extrafile';
+        $filerecord['sortorder'] = 0;
+        $fs->create_file_from_string($filerecord, 'Test resource file 2');
+
+        $resource = $this->getDataGenerator()->create_module('resource', $record);
+
+        $searcharea = \core_search\manager::get_search_area($this->resourceareaid);
+        $this->assertInstanceOf('\mod_resource\search\activity', $searcharea);
+
+        $recordset = $searcharea->get_recordset_by_timestamp(0);
+        $nrecords = 0;
+        foreach ($recordset as $record) {
+            $doc = $searcharea->get_document($record);
+            $searcharea->attach_files($doc);
+            $files = $doc->get_files();
+
+            // Resources should only return their main file.
+            $this->assertCount(1, $files);
+            $file = reset($files);
+            $this->assertEquals('mainfile', $file->get_filename());
+
+            $nrecords++;
+        }
+
+        $recordset->close();
+        $this->assertEquals(1, $nrecords);
+    }
+
+}
index e0569fd..d735172 100644 (file)
@@ -185,7 +185,7 @@ function SCORMapi1_3(def, cmiobj, cmiint, cmicommentsuser, cmicommentslms, scorm
             'cmi.interactions.n.result':{'pattern':CMIIndex, 'format':CMIResult, 'mod':'rw'},
             'cmi.interactions.n.latency':{'pattern':CMIIndex, 'format':CMITimespan, 'mod':'rw'},
             'cmi.interactions.n.description':{'pattern':CMIIndex, 'format':CMILangString250, 'mod':'rw'},
-            'cmi.launch_data':{'defaultvalue':def[scoid]['cmi.exit'], 'mod':'r'},
+            'cmi.launch_data':{'defaultvalue':def[scoid]['cmi.launch_data'], 'mod':'r'},
             'cmi.learner_id':{'defaultvalue':def[scoid]['cmi.learner_id'], 'mod':'r'},
             'cmi.learner_name':{'defaultvalue':def[scoid]['cmi.learner_name'], 'mod':'r'},
             'cmi.learner_preference._children':{'defaultvalue':student_preference_children, 'mod':'r'},
index 0f79ace..71ee823 100644 (file)
@@ -1280,6 +1280,7 @@ function get_scorm_default (&$userdata, $scorm, $scoid, $attempt, $mode) {
     $def['cmi.suspend_data'] = scorm_isset($userdata, 'cmi.suspend_data');
     $def['cmi.time_limit_action'] = scorm_isset($userdata, 'timelimitaction');
     $def['cmi.total_time'] = scorm_isset($userdata, 'cmi.total_time', 'PT0H0M0S');
+    $def['cmi.launch_data'] = scorm_isset($userdata, 'datafromlms');
 
     return $def;
 }
index f9c876f..bc60f0e 100644 (file)
@@ -41,10 +41,11 @@ class activity extends \core_search\area\base_activity {
      * Overwrites base_activity to add the provided URL as description.
      *
      * @param stdClass $record
+     * @param array    $options
      * @return \core_search\document
      */
-    public function get_document($record) {
-        $doc = parent::get_document($record);
+    public function get_document($record, $options = array()) {
+        $doc = parent::get_document($record, $options);
         if (!$doc) {
             return false;
         }
index b387765..4f44dc3 100644 (file)
@@ -3,7 +3,6 @@
 Amazon S3 PHP Class
 
 Cloned from git://github.com/tpyo/amazon-s3-php-class.git
-At commit 8413f6f70ad3bb79ae756958d4ba2238514b00af
 
 https://github.com/tpyo/amazon-s3-php-class
 http://undesigned.org.za/2007/10/22/amazon-s3-php-class
index 0b1564b..d8048c7 100644 (file)
@@ -32,7 +32,7 @@
 * Amazon S3 PHP class
 *
 * @link http://undesigned.org.za/2007/10/22/amazon-s3-php-class
-* @version 0.5.1-dev
+* @version 0.5.1
 */
 class S3
 {
@@ -56,7 +56,7 @@ class S3
         * @static
         */
        private static $__accessKey = null;
-       
+
        /**
         * AWS Secret Key
         *
@@ -65,7 +65,7 @@ class S3
         * @static
         */
        private static $__secretKey = null;
-       
+
        /**
         * SSL Client key
         *
@@ -74,7 +74,15 @@ class S3
         * @static
         */
        private static $__sslKey = null;
-       
+
+       /**
+        * Default delimiter to be used, for example while getBucket().
+        * @var string
+        * @access public
+        * @static
+        */
+       public static $defDelimiter = null;
+
        /**
         * AWS URI
         *
@@ -83,7 +91,7 @@ class S3
         * @static
         */
        public static $endpoint = 's3.amazonaws.com';
-       
+
        /**
         * Proxy information
         *
@@ -92,7 +100,7 @@ class S3
         * @static
         */
        public static $proxy = null;
-       
+
        /**
         * Connect using SSL?
         *
@@ -101,7 +109,7 @@ class S3
         * @static
         */
        public static $useSSL = false;
-       
+
        /**
         * Use SSL validation?
         *
@@ -110,7 +118,16 @@ class S3
         * @static
         */
        public static $useSSLValidation = true;
-       
+
+       /**
+        * Use SSL version
+        *
+        * @var const
+        * @access public
+        * @static
+        */
+       public static $useSSLVersion = CURL_SSLVERSION_TLSv1;
+
        /**
         * Use PHP exceptions?
         *
@@ -202,6 +219,7 @@ class S3
                self::$endpoint = $host;
        }
 
+
        /**
        * Set AWS access key and secret key
        *
@@ -416,6 +434,7 @@ class S3
                if ($marker !== null && $marker !== '') $rest->setParameter('marker', $marker);
                if ($maxKeys !== null && $maxKeys !== '') $rest->setParameter('max-keys', $maxKeys);
                if ($delimiter !== null && $delimiter !== '') $rest->setParameter('delimiter', $delimiter);
+               else if (!empty(self::$defDelimiter)) $rest->setParameter('delimiter', self::$defDelimiter);
                $response = $rest->getResponse();
                if ($response->error === false && $response->code !== 200)
                        $response->error = array('code' => $response->code, 'message' => 'Unexpected HTTP status');
@@ -561,6 +580,7 @@ class S3
                        self::__triggerError('S3::inputFile(): Unable to open input file: '.$file, __FILE__, __LINE__);
                        return false;
                }
+               clearstatcache(false, $file);
                return array('file' => $file, 'size' => filesize($file), 'md5sum' => $md5sum !== false ?
                (is_string($md5sum) ? $md5sum : base64_encode(md5_file($file, true))) : '');
        }
@@ -634,15 +654,18 @@ class S3
                if (isset($input['size']) && $input['size'] >= 0)
                        $rest->size = $input['size'];
                else {
-                       if (isset($input['file']))
+                       if (isset($input['file'])) {
+                               clearstatcache(false, $input['file']);
                                $rest->size = filesize($input['file']);
+                       }
                        elseif (isset($input['data']))
                                $rest->size = strlen($input['data']);
                }
 
                // Custom request headers (Content-Type, Content-Disposition, Content-Encoding)
                if (is_array($requestHeaders))
-                       foreach ($requestHeaders as $h => $v) $rest->setHeader($h, $v);
+                       foreach ($requestHeaders as $h => $v)
+                               strpos($h, 'x-amz-') === 0 ? $rest->setAmzHeader($h, $v) : $rest->setHeader($h, $v);
                elseif (is_string($requestHeaders)) // Support for legacy contentType parameter
                        $input['type'] = $requestHeaders;
 
@@ -797,7 +820,8 @@ class S3
        {
                $rest = new S3Request('PUT', $bucket, $uri, self::$endpoint);
                $rest->setHeader('Content-Length', 0);
-               foreach ($requestHeaders as $h => $v) $rest->setHeader($h, $v);
+               foreach ($requestHeaders as $h => $v)
+                               strpos($h, 'x-amz-') === 0 ? $rest->setAmzHeader($h, $v) : $rest->setHeader($h, $v);
                foreach ($metaHeaders as $h => $v) $rest->setAmzHeader('x-amz-meta-'.$h, $v);
                if ($storageClass !== self::STORAGE_CLASS_STANDARD) // Storage class
                        $rest->setAmzHeader('x-amz-storage-class', $storageClass);
@@ -2117,6 +2141,9 @@ final class S3Request
 
                if (S3::$useSSL)
                {
+                       // Set protocol version
+                       curl_setopt($curl, CURLOPT_SSLVERSION, S3::$useSSLVersion);
+
                        // SSL Validation can now be optional for those with broken OpenSSL installations
                        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, S3::$useSSLValidation ? 2 : 0);
                        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, S3::$useSSLValidation ? 1 : 0);
@@ -2292,6 +2319,7 @@ final class S3Request
        private function __dnsBucketName($bucket)
        {
                if (strlen($bucket) > 63 || preg_match("/[^a-z0-9\.-]/", $bucket) > 0) return false;
+               if (S3::$useSSL && strstr($bucket, '.') !== false) return false;
                if (strstr($bucket, '-.') !== false) return false;
                if (strstr($bucket, '..') !== false) return false;
                if (!preg_match("/^[0-9a-z]/", $bucket)) return false;
index efb9909..ea87af0 100644 (file)
 require_once($CFG->dirroot . '/repository/lib.php');
 require_once($CFG->dirroot . '/repository/s3/S3.php');
 
+// This constant is not defined in php 5.4. Set it to avoid errors.
+if (!defined('CURL_SSLVERSION_TLSv1')) {
+    define('CURL_SSLVERSION_TLSv1', 1);
+}
+
 /**
  * This is a repository class used to browse Amazon S3 content.
  *
index f53ed95..04bb560 100644 (file)
@@ -4,7 +4,7 @@
     <location>S3.php</location>
     <name>S3</name>
     <license>BSD</license>
-    <version>0.5.1-dev</version>
+    <version>0.5.1</version>
     <licenseversion></licenseversion>
   </library>
 </libraries>
index df6d1fa..8cba240 100644 (file)
@@ -26,6 +26,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2015111600;        // The current plugin version (Date: YYYYMMDDXX)
+$plugin->version   = 2015111601;        // The current plugin version (Date: YYYYMMDDXX)
 $plugin->requires  = 2015111000;        // Requires this Moodle version
 $plugin->component = 'repository_s3';   // Full name of the plugin (used for diagnostics)
index 0976bcf..78aa3b8 100644 (file)
@@ -185,6 +185,15 @@ abstract class base {
         return (bool)get_config($componentname, $varname . '_enabled');
     }
 
+    /**
+     * Returns true if this area uses file indexing.
+     *
+     * @return bool
+     */
+    public function uses_file_indexing() {
+        return false;
+    }
+
     /**
      * Returns a recordset ordered by modification date ASC.
      *
@@ -213,10 +222,25 @@ abstract class base {
      * Search areas should send plain text to the search engine, use the following function to convert any user
      * input data to plain text: {@link content_to_text}
      *
+     * Valid keys for the options array are:
+     *     indexfiles => File indexing is enabled if true.
+     *     lastindexedtime => The last time this area was indexed. 0 if never indexed.
+     *
      * @param \stdClass $record A record containing, at least, the indexed document id and a modified timestamp
+     * @param array     $options Options for document creation
      * @return \core_search\document
      */
-    abstract public function get_document($record);
+    abstract public function get_document($record, $options = array());
+
+    /**
+     * Add any files to the document that should be indexed.
+     *
+     * @param document $document The current document
+     * @return void
+     */
+    public function attach_files($document) {
+        return;
+    }
 
     /**
      * Can the current user see the document.
index 2b35049..e412587 100644 (file)
@@ -43,6 +43,11 @@ abstract class base_activity extends base_mod {
      */
     const MODIFIED_FIELD_NAME = 'timemodified';
 
+    /**
+     * Activities with a time created field can overwrite this constant.
+     */
+    const CREATED_FIELD_NAME = '';
+
     /**
      * The context levels the search area is working on.
      * @var array
@@ -68,9 +73,10 @@ abstract class base_activity extends base_mod {
      * default ones, or to fill description optional fields with extra stuff.
      *
      * @param stdClass $record
+     * @param array    $options
      * @return \core_search\document
      */
-    public function get_document($record) {
+    public function get_document($record, $options = array()) {
 
         try {
             $cm = $this->get_cm($this->get_module_name(), $record->id, $record->course);
@@ -91,11 +97,19 @@ abstract class base_activity extends base_mod {
         $doc->set('title', $record->name);
         $doc->set('content', content_to_text($record->intro, $record->introformat));
         $doc->set('contextid', $context->id);
-        $doc->set('type', \core_search\manager::TYPE_TEXT);
         $doc->set('courseid', $record->course);
         $doc->set('owneruserid', \core_search\manager::NO_OWNER_ID);
         $doc->set('modified', $record->{static::MODIFIED_FIELD_NAME});
 
+        // Check if this document should be considered new.
+        if (isset($options['lastindexedtime'])) {
+            $createdfield = static::CREATED_FIELD_NAME;
+            if (!empty($createdfield) && ($options['lastindexedtime'] < $record->{$createdfield})) {
+                // If the document was created after the last index time, it must be new.
+                $doc->set_is_new(true);
+            }
+        }
+
         return $doc;
     }
 
index 10781ce..288cb1a 100644 (file)
@@ -63,13 +63,12 @@ abstract class base_mod extends base {
      * @return \cm_info
      */
     protected function get_cm($modulename, $instanceid, $courseid) {
-
         $modinfo = get_fast_modinfo($courseid);
 
         // Hopefully not many, they are indexed by cmid.
         $instances = $modinfo->get_instances_of($modulename);
         foreach ($instances as $cminfo) {
-            if ($cminfo->instance === $instanceid) {
+            if ($cminfo->instance == $instanceid) {
                 return $cminfo;
             }
         }
index 9d47ab9..db0da71 100644 (file)
@@ -68,6 +68,16 @@ class document implements \renderable, \templatable {
      */
     protected $contentitemid = null;
 
+    /**
+     * @var bool Should be set to true if document hasn't been indexed before. False if unknown.
+     */
+    protected $isnew = false;
+
+    /**
+     * @var \stored_file[] An array of stored files to attach to the document.
+     */
+    protected $files = array();
+
     /**
      * All required fields any doc should contain.
      *
@@ -91,12 +101,12 @@ class document implements \renderable, \templatable {
             'indexed' => true
         ),
         'title' => array(
-            'type' => 'string',
+            'type' => 'text',
             'stored' => true,
             'indexed' => true
         ),
         'content' => array(
-            'type' => 'string',
+            'type' => 'text',
             'stored' => true,
             'indexed' => true
         ),
@@ -118,7 +128,7 @@ class document implements \renderable, \templatable {
         'courseid' => array(
             'type' => 'int',
             'stored' => true,
-            'indexed' => false
+            'indexed' => true
         ),
         'owneruserid' => array(
             'type' => 'int',
@@ -145,20 +155,32 @@ class document implements \renderable, \templatable {
         'userid' => array(
             'type' => 'int',
             'stored' => true,
-            'indexed' => false
+            'indexed' => true
         ),
         'description1' => array(
-            'type' => 'string',
+            'type' => 'text',
             'stored' => true,
             'indexed' => true
         ),
         'description2' => array(
-            'type' => 'string',
+            'type' => 'text',
             'stored' => true,
             'indexed' => true
-        ),
+        )
     );
 
+    /**
+     * Any fields that are engine specifc. These are fields that are solely used by a search engine plugin
+     * for internal purposes.
+     *
+     * Field names should be prefixed with engine name to avoid potential conflict with core fields.
+     *
+     * Uses same format as fields above.
+     *
+     * @var array
+     */
+    protected static $enginefields = array();
+
     /**
      * We ensure that the document has a unique id across search areas.
      *
@@ -178,6 +200,40 @@ class document implements \renderable, \templatable {
         $this->data['itemid'] = intval($itemid);
     }
 
+    /**
+     * Add a stored file to the document.
+     *
+     * @param \stored_file|int $file The file to add, or file id.
+     * @return void
+     */
+    public function add_stored_file($file) {
+        if (is_numeric($file)) {
+            $this->files[$file] = $file;
+        } else {
+            $this->files[$file->get_id()] = $file;
+        }
+    }
+
+    /**
+     * Returns the array of attached files.
+     *
+     * @return \stored_file[]
+     */
+    public function get_files() {
+        // The files array can contain stored file ids, so we need to get instances if asked.
+        foreach ($this->files as $id => $listfile) {
+            if (is_numeric($listfile)) {
+                $fs = get_file_storage();
+
+                if ($file = $fs->get_file_by_id($id)) {
+                    $this->files[$id] = $file;
+                }
+            }
+        }
+
+        return $this->files;
+    }
+
     /**
      * Setter.
      *
@@ -197,6 +253,8 @@ class document implements \renderable, \templatable {
             $fielddata = static::$requiredfields[$fieldname];
         } else if (!empty(static::$optionalfields[$fieldname])) {
             $fielddata = static::$optionalfields[$fieldname];
+        } else if (!empty(static::$enginefields[$fieldname])) {
+            $fielddata = static::$enginefields[$fieldname];
         }
 
         if (empty($fielddata)) {
@@ -268,13 +326,31 @@ class document implements \renderable, \templatable {
         return (isset($this->data[$field]) || isset($this->extradata[$field]));
     }
 
+    /**
+     * Set if this is a new document. False if unknown.
+     *
+     * @param bool $new
+     */
+    public function set_is_new($new) {
+       $this->isnew = (bool)$new;
+    }
+
+    /**
+     * Returns if the document is new. False if unknown.
+     *
+     * @return bool
+     */
+    public function get_is_new() {
+       return $this->isnew;
+    }
+
     /**
      * Returns all default fields definitions.
      *
      * @return array
      */
     public static function get_default_fields_definition() {
-        return static::$requiredfields + static::$optionalfields;
+        return static::$requiredfields + static::$optionalfields + static::$enginefields;
     }
 
     /**
@@ -305,6 +381,19 @@ class document implements \renderable, \templatable {
         return $string;
     }
 
+    /**
+     * Formats a text value for the search engine.
+     *
+     * Search engines may overwrite this method to apply restrictions, like limiting the size.
+     * The default behaviour is just returning the string.
+     *
+     * @param string $text
+     * @return string
+     */
+    public static function format_text_for_engine($text) {
+        return $text;
+    }
+
     /**
      * Returns a timestamp from the value stored in the search engine.
      *
@@ -337,7 +426,7 @@ class document implements \renderable, \templatable {
      * @return void
      */
     public function set_data_from_engine($docdata) {
-        $fields = static::$requiredfields + static::$optionalfields;
+        $fields = static::$requiredfields + static::$optionalfields + static::$enginefields;
         foreach ($fields as $fieldname => $field) {
 
             // Optional params might not be there.
@@ -395,6 +484,8 @@ class document implements \renderable, \templatable {
      * @return array
      */
     public function export_for_engine() {
+        // Set any unset defaults.
+        $this->apply_defaults();
 
         // We don't want to affect the document instance.
         $data = $this->data;
@@ -411,12 +502,17 @@ class document implements \renderable, \templatable {
                 // Overwrite the timestamp with the engine dependant format.
                 $data[$fieldname] = static::format_time_for_engine($data[$fieldname]);
             } else if ($field['type'] === 'string') {
-                // Overwrite the timestamp with the engine dependant format.
+                // Overwrite the string with the engine dependant format.
                 $data[$fieldname] = static::format_string_for_engine($data[$fieldname]);
+            } else if ($field['type'] === 'text') {
+                // Overwrite the text with the engine dependant format.
+                $data[$fieldname] = static::format_text_for_engine($data[$fieldname]);
             }
+
         }
 
-        foreach (static::$optionalfields as $fieldname => $field) {
+        $fields = static::$optionalfields + static::$enginefields;
+        foreach ($fields as $fieldname => $field) {
             if (!isset($data[$fieldname])) {
                 continue;
             }
@@ -424,14 +520,29 @@ class document implements \renderable, \templatable {
                 // Overwrite the timestamp with the engine dependant format.
                 $data[$fieldname] = static::format_time_for_engine($data[$fieldname]);
             } else if ($field['type'] === 'string') {
-                // Overwrite the timestamp with the engine dependant format.
+                // Overwrite the string with the engine dependant format.
                 $data[$fieldname] = static::format_string_for_engine($data[$fieldname]);
+            } else if ($field['type'] === 'text') {
+                // Overwrite the text with the engine dependant format.
+                $data[$fieldname] = static::format_text_for_engine($data[$fieldname]);
             }
         }
 
         return $data;
     }
 
+    /**
+     * Apply any defaults to unset fields before export. Called after document building, but before export.
+     *
+     * Sub-classes of this should make sure to call parent::apply_defaults().
+     */
+    protected function apply_defaults() {
+        // Set the default type, TYPE_TEXT.
+        if (!isset($this->data['type'])) {
+            $this->data['type'] = manager::TYPE_TEXT;
+        }
+    }
+
     /**
      * Export the document data to be used as a template context.
      *
@@ -444,14 +555,14 @@ class document implements \renderable, \templatable {
      * @return array
      */
     public function export_for_template(\renderer_base $output) {
-
         list($componentname, $areaname) = \core_search\manager::extract_areaid_parts($this->get('areaid'));
 
+        $title = $this->is_set('title') ? $this->format_text($this->get('title')) : '';
         $data = [
             'courseurl' => new \moodle_url('/course/view.php?id=' . $this->get('courseid')),
             'coursefullname' => format_string($this->get('coursefullname'), true, array('context' => $this->get('contextid'))),
             'modified' => userdate($this->get('modified')),
-            'title' => format_string($this->get('title'), true, array('context' => $this->get('contextid'))),
+            'title' => ($title !== '') ? $title : get_string('notitle', 'search'),
             'docurl' => $this->get_doc_url(),
             'content' => $this->is_set('content') ? $this->format_text($this->get('content')) : null,
             'contexturl' => $this->get_context_url(),
@@ -459,6 +570,22 @@ class document implements \renderable, \templatable {
             'description2' => $this->is_set('description2') ? $this->format_text($this->get('description2')) : null,
         ];
 
+        // Now take any attached any files.
+        $files = $this->get_files();
+        if (!empty($files)) {
+            if (count($files) > 1) {
+                $filenames = array();
+                foreach ($files as $file) {
+                    $filenames[] = $file->get_filename();
+                }
+                $data['multiplefiles'] = true;
+                $data['filenames'] = $filenames;
+            } else {
+                $file = reset($files);
+                $data['filename'] = $file->get_filename();
+            }
+        }
+
         if ($this->is_set('userid')) {
             $data['userurl'] = new \moodle_url('/user/view.php', array('id' => $this->get('userid'), 'course' => $this->get('courseid')));
             $data['userfullname'] = format_string($this->get('userfullname'), true, array('context' => $this->get('contextid')));
index 6452345..b157d83 100644 (file)
@@ -313,6 +313,15 @@ abstract class engine {
         return $this->queryerror;
     }
 
+    /**
+     * Return true if file indexing is supported and enabled. False otherwise.
+     *
+     * @return bool
+     */
+    public function file_indexing_enabled() {
+        return false;
+    }
+
     /**
      * Clears the current query error value.
      *
@@ -334,10 +343,11 @@ abstract class engine {
     /**
      * Adds a document to the search engine.
      *
-     * @param array $doc
-     * @return void
+     * @param document $document
+     * @param bool     $fileindexing True if file indexing is to be used
+     * @return bool    False if the file was skipped or failed, true on success
      */
-    abstract function add_document($doc);
+    abstract function add_document($document, $fileindexing = false);
 
     /**
      * Executes the query on the engine.
index 53be704..e4a1bee 100644 (file)
@@ -42,6 +42,11 @@ class manager {
      */
     const TYPE_TEXT = 1;
 
+    /**
+     * @var int File contents.
+     */
+    const TYPE_FILE = 2;
+
     /**
      * @var int User can not access the document.
      */
@@ -498,33 +503,40 @@ class manager {
             $numdocsignored = 0;
             $lastindexeddoc = 0;
 
+            $prevtimestart = intval(get_config($componentconfigname, $varname . '_indexingstart'));
+
             if ($fullindex === true) {
-                $prevtimestart = 0;
+                $referencestarttime = 0;
             } else {
-                $prevtimestart = intval(get_config($componentconfigname, $varname . '_indexingstart'));
+                $referencestarttime = $prevtimestart;
             }
 
             // Getting the recordset from the area.
-            $recordset = $searcharea->get_recordset_by_timestamp($prevtimestart);
+            $recordset = $searcharea->get_recordset_by_timestamp($referencestarttime);
 
             // Pass get_document as callback.
-            $iterator = new \core\dml\recordset_walk($recordset, array($searcharea, 'get_document'));
+            $fileindexing = $this->engine->file_indexing_enabled() && $searcharea->uses_file_indexing();
+            $options = array('indexfiles' => $fileindexing, 'lastindexedtime' => $prevtimestart);
+            $iterator = new \core\dml\recordset_walk($recordset, array($searcharea, 'get_document'), $options);
             foreach ($iterator as $document) {
-
                 if (!$document instanceof \core_search\document) {
                     continue;
                 }
 
-                $docdata = $document->export_for_engine();
-                switch ($docdata['type']) {
-                    case static::TYPE_TEXT:
-                        $this->engine->add_document($docdata);
-                        $numdocs++;
-                        break;
-                    default:
-                        $numdocsignored++;
-                        $iterator->close();
-                        throw new \moodle_exception('doctypenotsupported', 'search');
+                if ($prevtimestart == 0) {
+                    // If we have never indexed this area before, it must be new.
+                    $document->set_is_new(true);
+                }
+
+                if ($fileindexing) {
+                    // Attach files if we are indexing.
+                    $searcharea->attach_files($document);
+                }
+
+                if ($this->engine->add_document($document, $fileindexing)) {
+                    $numdocs++;
+                } else {
+                    $numdocsignored++;
                 }
 
                 $lastindexeddoc = $document->get('modified');
index 20c1f3e..f428dee 100644 (file)
@@ -33,6 +33,49 @@ defined('MOODLE_INTERNAL') || die();
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 class document extends \core_search\document {
+    /**
+     * Indicates the file contents were not indexed due to an error.
+     */
+    const INDEXED_FILE_ERROR = -1;
+
+    /**
+     * Indicates the file contents were not indexed due filtering/settings.
+     */
+    const INDEXED_FILE_FALSE = 0;
+
+    /**
+     * Indicates the file contents are indexed with the record.
+     */
+    const INDEXED_FILE_TRUE = 1;
+
+    /**
+     * Any fields that are engine specifc. These are fields that are solely used by a seach engine plugin
+     * for internal purposes.
+     *
+     * @var array
+     */
+    protected static $enginefields = array(
+        'solr_filegroupingid' => array(
+            'type' => 'string',
+            'stored' => true,
+            'indexed' => true
+        ),
+        'solr_fileid' => array(
+            'type' => 'string',
+            'stored' => true,
+            'indexed' => false
+        ),
+        'solr_filecontenthash' => array(
+            'type' => 'string',
+            'stored' => true,
+            'indexed' => false
+        ),
+        'solr_fileindexedcontent' => array(
+            'type' => 'int',
+            'stored' => true,
+            'indexed' => true
+        )
+    );
 
     /**
      * Formats the timestamp according to the search engine needs.
@@ -72,6 +115,80 @@ class document extends \core_search\document {
      * @return int
      */
     protected function get_text_format() {
-        return FORMAT_MARKDOWN;
+        return FORMAT_HTML;
+    }
+
+    /**
+     * Formats a text string coming from the search engine.
+     *
+     * @param  string $text Text to format
+     * @return string HTML text to be renderer
+     */
+    protected function format_text($text) {
+        // Since we allow output for highlighting, we need to encode html entities.
+        // This ensures plaintext html chars don't become valid html.
+        $out = s($text);
+
+        $startcount = 0;
+        $endcount = 0;
+
+        // Remove end/start pairs that span a few common seperation characters. Allows us to highlight phrases instead of words.
+        $regex = '|'.engine::HIGHLIGHT_END.'([ .,-]{0,3})'.engine::HIGHLIGHT_START.'|';
+        $out = preg_replace($regex, '$1', $out);
+
+        // Now replace our start and end highlight markers.
+        $out = str_replace(engine::HIGHLIGHT_START, '<span class="highlight">', $out, $startcount);
+        $out = str_replace(engine::HIGHLIGHT_END, '</span>', $out, $endcount);
+
+        // This makes sure any highlight tags are balanced, incase truncation or the highlight text contained our markers.
+        while ($startcount > $endcount) {
+            $out .= '</span>';
+            $endcount++;
+        }
+        while ($startcount < $endcount) {
+            $out = '<span class="highlight">' . $out;
+            $endcount++;
+        }
+
+        return parent::format_text($out);
+    }
+
+    /**
+     * Apply any defaults to unset fields before export. Called after document building, but before export.
+     *
+     * Sub-classes of this should make sure to call parent::apply_defaults().
+     */
+    protected function apply_defaults() {
+        parent::apply_defaults();
+
+        // We want to set the solr_filegroupingid to id if it isn't set.
+        if (!isset($this->data['solr_filegroupingid'])) {
+            $this->data['solr_filegroupingid'] = $this->data['id'];
+        }
+    }
+
+    /**
+     * Export the data for the given file in relation to this document.
+     *
+     * @param \stored_file $file The stored file we are talking about.
+     * @return array
+     */
+    public function export_file_for_engine($file) {
+        $data = $this->export_for_engine();
+
+        // Content is index in the main document.
+        unset($data['content']);
+        unset($data['description1']);
+        unset($data['description2']);
+
+        // Going to append the fileid to give it a unique id.
+        $data['id'] = $data['id'].'-solrfile'.$file->get_id();
+        $data['type'] = \core_search\manager::TYPE_FILE;
+        $data['solr_fileid'] = $file->get_id();
+        $data['solr_filecontenthash'] = $file->get_contenthash();
+        $data['solr_fileindexedcontent'] = self::INDEXED_FILE_TRUE;
+        $data['title'] = $file->get_filename();
+
+        return $data;
     }
 }
index cd2ccd8..bdc2998 100644 (file)
@@ -46,9 +46,19 @@ class engine extends \core_search\engine {
     const AUTOCOMMIT_WITHIN = 15000;
 
     /**
-     * @var int Highlighting fragsize.
+     * Highlighting fragsize. Slightly larger than output size (500) to allow for ... appending.
      */
-    const FRAG_SIZE = 500;
+    const FRAG_SIZE = 510;
+
+    /**
+     * Marker for the start of a highlight.
+     */
+    const HIGHLIGHT_START = '@@HI_S@@';
+
+    /**
+     * Marker for the end of a highlight.
+     */
+    const HIGHLIGHT_END = '@@HI_E@@';
 
     /**
      * @var \SolrClient
@@ -63,7 +73,7 @@ class engine extends \core_search\engine {
     /**
      * @var array Fields that can be highlighted.
      */
-    protected $highlightfields = array('content', 'description1', 'description2');
+    protected $highlightfields = array('title', 'content', 'description1', 'description2');
 
     /**
      * Prepares a Solr query, applies filters and executes it returning its results.
@@ -88,7 +98,12 @@ class engine extends \core_search\engine {
         }
 
         $query = new \SolrQuery();
-        $this->set_query($query, $data->q);
+        $maxrows = \core_search\manager::MAX_RESULTS;
+        if ($this->file_indexing_enabled()) {
+            // When using file indexing and grouping, we are going to collapse results, so we want extra results.
+            $maxrows *= 2;
+        }
+        $this->set_query($query, $data->q, $maxrows);
         $this->add_fields($query);
 
         // Search filters applied, we don't cache these filters as we don't want to pollute the cache with tmp filters
@@ -140,7 +155,15 @@ class engine extends \core_search\engine {
         }
 
         try {
-            return $this->query_response($this->client->query($query));
+            if ($this->file_indexing_enabled()) {
+                // Now group records by solr_filegroupingid. Limit to 3 results per group.
+                $query->setGroup(true);
+                $query->setGroupLimit(3);
+                $query->addGroupField('solr_filegroupingid');
+                return $this->grouped_files_query_response($this->client->query($query));
+            } else {
+                return $this->query_response($this->client->query($query));
+            }
         } catch (\SolrClientException $ex) {
             debugging('Error executing the provided query: ' . $ex->getMessage(), DEBUG_DEVELOPER);
             $this->queryerror = $ex->getMessage();
@@ -156,9 +179,13 @@ class engine extends \core_search\engine {
     /**
      * Prepares a new query by setting the query, start offset and rows to return.
      * @param SolrQuery $query
-     * @param object $q Containing query and filters.
+     * @param object    $q Containing query and filters.
+     * @param null|int  $maxresults The number of results to limit. manager::MAX_RESULTS if not set.
      */
-    protected function set_query($query, $q) {
+    protected function set_query($query, $q, $maxresults = null) {
+        if (!is_numeric($maxresults)) {
+            $maxresults = \core_search\manager::MAX_RESULTS;
+        }
 
         // Set hightlighting.
         $query->setHighlight(true);
@@ -166,13 +193,14 @@ class engine extends \core_search\engine {
             $query->addHighlightField($field);
         }
         $query->setHighlightFragsize(static::FRAG_SIZE);
-        $query->setHighlightSimplePre('__');
-        $query->setHighlightSimplePost('__');
+        $query->setHighlightSimplePre(self::HIGHLIGHT_START);
+        $query->setHighlightSimplePost(self::HIGHLIGHT_END);
+        $query->setHighlightMergeContiguous(true);
 
         $query->setQuery($q);
 
         // A reasonable max.
-        $query->setRows(\core_search\manager::MAX_RESULTS);
+        $query->setRows($maxresults);
     }
 
     /**
@@ -193,6 +221,11 @@ class engine extends \core_search\engine {
      * @param object $response containing results.
      */
     public function add_highlight_content($response) {
+        if (!isset($response->highlighting)) {
+            // There is no highlighting to add.
+            return;
+        }
+
         $highlightedobject = $response->highlighting;
         foreach ($response->response->docs as $doc) {
             $x = $doc->id;
@@ -291,6 +324,155 @@ class engine extends \core_search\engine {
         return $docs;
     }
 
+    /**
+     * Processes grouped file results into documents, with attached matching files.
+     *
+     * @param SolrQueryResponse $queryresponse The response returned from solr server
+     * @return array Final results to be displayed.
+     */
+    protected function grouped_files_query_response($queryresponse) {
+        $response = $queryresponse->getResponse();
+
+        // If we can't find the grouping, or there are no matches in the grouping, return empty.
+        if (!isset($response->grouped->solr_filegroupingid) || empty($response->grouped->solr_filegroupingid->matches)) {
+            return array();
+        }
+
+        $numgranted = 0;
+        $orderedids = array();
+        $completedocs = array();
+        $incompletedocs = array();
+
+        $highlightingobj = $response->highlighting;
+
+        // Each group represents a "master document".
+        $groups = $response->grouped->solr_filegroupingid->groups;
+        foreach ($groups as $group) {
+            $groupid = $group->groupValue;
+            $groupdocs = $group->doclist->docs;
+            $firstdoc = reset($groupdocs);
+
+            if (!$searcharea = $this->get_search_area($firstdoc->areaid)) {
+                // Well, this is a problem.
+                continue;
+            }
+
+            // Check for access.
+            $access = $searcharea->check_access($firstdoc->itemid);
+            switch ($access) {
+                case \core_search\manager::ACCESS_DELETED:
+                    // If deleted from Moodle, delete from index and then continue.
+                    $this->delete_by_id($firstdoc->id);
+                    continue 2;
+                    break;
+                case \core_search\manager::ACCESS_DENIED:
+                    // This means we should just skip for the current user.
+                    continue 2;
+                    break;
+            }
+            $numgranted++;
+
+            $maindoc = false;
+            $fileids = array();
+            // Seperate the main document and any files returned.
+            foreach ($groupdocs as $groupdoc) {
+                if ($groupdoc->id == $groupid) {
+                    $maindoc = $groupdoc;
+                } else if (isset($groupdoc->solr_fileid)) {
+                    $fileids[] = $groupdoc->solr_fileid;
+                }
+            }
+
+            // Store the id of this group, in order, for later merging.
+            $orderedids[] = $groupid;
+
+            if (!$maindoc) {
+                // We don't have the main doc, store what we know for later building.
+                $incompletedocs[$groupid] = $fileids;
+            } else {
+                if (isset($highlightingobj->$groupid)) {
+                    // Merge the highlighting for this doc.
+                    $this->merge_highlight_field_values($maindoc, $highlightingobj->$groupid);
+                }
+                $docdata = $this->standarize_solr_obj($maindoc);
+                $doc = $this->to_document($searcharea, $docdata);
+                // Now we need to attach the result files to the doc.
+                foreach ($fileids as $fileid) {
+                    $doc->add_stored_file($fileid);
+                }
+                $completedocs[$groupid] = $doc;
+            }
+
+            if ($numgranted >= \core_search\manager::MAX_RESULTS) {
+                // We have hit the max results, we will just ignore the rest.
+                break;
+            }
+        }
+
+        $incompletedocs = $this->get_missing_docs($incompletedocs);
+
+        $out = array();
+        // Now merge the complete and incomplete documents, in results order.
+        foreach ($orderedids as $docid) {
+            if (isset($completedocs[$docid])) {
+                $out[] = $completedocs[$docid];
+            } else if (isset($incompletedocs[$docid])) {
+                $out[] = $incompletedocs[$docid];
+            }
+        }
+
+        return $out;
+    }
+
+    /**
+     * Retreive any missing main documents and attach provided files.
+     *
+     * The missingdocs array should be an array, indexed by document id, of main documents we need to retrieve. The value
+     * associated to the key should be an array of stored_files or stored file ids to attach to the result document.
+     *
+     * Return array also indexed by document id.
+     *
+     * @param array() $missingdocs An array, indexed by document id, with arrays of files/ids to attach.
+     * @return document[]
+     */
+    protected function get_missing_docs($missingdocs) {
+        if (empty($missingdocs)) {
+            return array();
+        }
+
+        $docids = array_keys($missingdocs);
+
+        // Build a custom query that will get all the missing documents.
+        $query = new \SolrQuery();
+        $this->set_query($query, '*', count($docids));
+        $this->add_fields($query);
+        $query->addFilterQuery('{!cache=false}id:(' . implode(' OR ', $docids) . ')');
+
+        try {
+            $results = $this->query_response($this->get_search_client()->query($query));
+        } catch (\SolrClientException $ex) {
+            return array();
+        } catch (\SolrServerException $ex) {
+            return array();
+        }
+
+        $out = array();
+        foreach ($results as $result) {
+            $resultid = $result->get('id');
+            if (!isset($missingdocs[$resultid])) {
+                // We got a result we didn't expect. Skip it.
+                continue;
+            }
+            // Attach the files.
+            foreach ($missingdocs[$resultid] as $filedoc) {
+                $result->add_stored_file($filedoc);
+            }
+            $out[$resultid] = $result;
+        }
+
+        return $out;
+    }
+
     /**
      * Returns a standard php array from a \SolrObject instance.
      *
@@ -314,11 +496,32 @@ class engine extends \core_search\engine {
      *
      * This does not commit to the search engine.
      *
-     * @param array $doc
-     * @return void
+     * @param document $document
+     * @param bool     $fileindexing True if file indexing is to be used
+     * @return bool
      */
-    public function add_document($doc) {
+    public function add_document($document, $fileindexing = false) {
+        $docdata = $document->export_for_engine();
 
+        if (!$this->add_solr_document($docdata)) {
+            return false;
+        }
+
+        if ($fileindexing) {
+            // This will take care of updating all attached files in the index.
+            $this->process_document_files($document);
+        }
+
+        return true;
+    }
+
+    /**
+     * Adds a text document to the search engine.
+     *
+     * @param array $doc
+     * @return bool
+     */
+    protected function add_solr_document($doc) {
         $solrdoc = new \SolrInputDocument();
         foreach ($doc as $field => $value) {
             $solrdoc->addField($field, $value);
@@ -326,6 +529,7 @@ class engine extends \core_search\engine {
 
         try {
             $result = $this->get_search_client()->addDocument($solrdoc, true, static::AUTOCOMMIT_WITHIN);
+            return true;
         } catch (\SolrClientException $e) {
             debugging('Solr client error adding document with id ' . $doc['id'] . ': ' . $e->getMessage(), DEBUG_DEVELOPER);
         } catch (\SolrServerException $e) {
@@ -333,6 +537,295 @@ class engine extends \core_search\engine {
             $msg = strtok($e->getMessage(), "\n");
             debugging('Solr server error adding document with id ' . $doc['id'] . ': ' . $msg, DEBUG_DEVELOPER);
         }
+
+        return false;
+    }
+
+    /**
+     * Index files attached to the docuemnt, ensuring the index matches the current document files.
+     *
+     * For documents that aren't known to be new, we check the index for existing files.
+     * - New files we will add.
+     * - Existing and unchanged files we will skip.
+     * - File that are in the index but not on the document will be deleted from the index.
+     * - Files that have changed will be re-indexed.
+     *
+     * @param document $document
+     */
+    protected function process_document_files($document) {
+        if (!$this->file_indexing_enabled()) {
+            return;
+        }
+
+        // Maximum rows to process at a time.
+        $rows = 500;
+
+        // Get the attached files.
+        $files = $document->get_files();
+
+        // If this isn't a new document, we need to check the exiting indexed files.
+        if (!$document->get_is_new()) {
+            // We do this progressively, so we can handle lots of files cleanly.
+            list($numfound, $indexedfiles) = $this->get_indexed_files($document, 0, $rows);
+            $count = 0;
+            $idstodelete = array();
+
+            do {
+                // Go through each indexed file. We want to not index any stored and unchanged ones, delete any missing ones.
+                foreach ($indexedfiles as $indexedfile) {
+                    $fileid = $indexedfile->solr_fileid;
+
+                    if (isset($files[$fileid])) {
+                        // Check for changes that would mean we need to re-index the file. If so, just leave in $files.
+                        // Filelib does not guarantee time modified is updated, so we will check important values.
+                        if ($indexedfile->modified < $files[$fileid]->get_timemodified()) {
+                            continue;
+                        }
+                        if (strcmp($indexedfile->title, $files[$fileid]->get_filename()) !== 0) {
+                            continue;
+                        }
+                        if ($indexedfile->solr_filecontenthash != $files[$fileid]->get_contenthash()) {
+                            continue;
+                        }
+                        if ($indexedfile->solr_fileindexedcontent == document::INDEXED_FILE_FALSE &&
+                                $this->file_is_indexable($files[$fileid])) {
+                            // This means that the last time we indexed this file, filtering blocked it.
+                            // Current settings say it is indexable, so we will allow it to be indexed.
+                            continue;
+                        }
+
+                        // If the file is already indexed, we can just remove it from the files array and skip it.
+                        unset($files[$fileid]);
+                    } else {
+                        // This means we have found a file that is no longer attached, so we need to delete from the index.
+                        // We do it later, since this is progressive, and it could reorder results.
+                        $idstodelete[] = $indexedfile->id;
+                    }
+                }
+                $count += $rows;
+
+                if ($count < $numfound) {
+                    // If we haven't hit the total count yet, fetch the next batch.
+                    list($numfound, $indexedfiles) = $this->get_indexed_files($document, $count, $rows);
+                }
+
+            } while ($count < $numfound);
+
+            // Delete files that are no longer attached.
+            foreach ($idstodelete as $id) {
+                // We directly delete the item using the client, as the engine delete_by_id won't work on file docs.
+                $this->get_search_client()->deleteById($id);
+            }
+        }
+
+        // Now we can actually index all the remaining files.
+        foreach ($files as $file) {
+            $this->add_stored_file($document, $file);
+        }
+    }
+
+    /**
+     * Get the currently indexed files for a particular document, returns the total count, and a subset of files.
+     *
+     * @param document $document
+     * @param int      $start The row to start the results on. Zero indexed.
+     * @param int      $rows The number of rows to fetch
+     * @return array   A two element array, the first is the total number of availble results, the second is an array
+     *                 of documents for the current request.
+     */
+    protected function get_indexed_files($document, $start = 0, $rows = 500) {
+        // Build a custom query that will get any document files that are in our solr_filegroupingid.
+        $query = new \SolrQuery();
+
+        // We want to get all file records tied to a document.
+        // For efficiency, we are building our own, stripped down, query.
+        $query->setQuery('*');
+        $query->setRows($rows);
+        $query->setStart($start);
+        // We want a consistent sorting.
+        $query->addSortField('id');
+
+        // We only want the bare minimum of fields.
+        $query->addField('id');
+        $query->addField('modified');
+        $query->addField('title');
+        $query->addField('solr_fileid');
+        $query->addField('solr_filecontenthash');
+        $query->addField('solr_fileindexedcontent');
+
+        $query->addFilterQuery('{!cache=false}solr_filegroupingid:(' . $document->get('id') . ')');
+        $query->addFilterQuery('type:' . \core_search\manager::TYPE_FILE);
+
+        try {
+            $response = $this->get_search_client()->query($query);
+            $responsedoc = $response->getResponse();
+
+            if (empty($responsedoc->response->numFound)) {
+                return array(0, array());
+            }
+            $numfound = $responsedoc->response->numFound;
+
+            return array($numfound, $this->convert_file_results($responsedoc));
+        } catch (\SolrClientException $ex) {
+            debugging('Error executing the provided query: ' . $ex->getMessage(), DEBUG_DEVELOPER);
+            $this->queryerror = $ex->getMessage();
+            return array(0, array());
+        } catch (\SolrServerException $ex) {
+            debugging('Error executing the provided query: ' . $ex->getMessage(), DEBUG_DEVELOPER);
+            $this->queryerror = $ex->getMessage();
+            return array(0, array());
+        }
+    }
+
+    /**
+     * A very lightweight handler for getting information about already indexed files from a Solr response.
+     *
+     * @param SolrObject $responsedoc A Solr response document
+     * @return stdClass[] An array of objects that contain the basic information for file processing.
+     */
+    protected function convert_file_results($responsedoc) {
+        if (!$docs = $responsedoc->response->docs) {
+            return array();
+        }
+
+        $out = array();
+
+        foreach ($docs as $doc) {
+            // Copy the bare minimim needed info.
+            $result = new \stdClass();
+            $result->id = $doc->id;
+            $result->modified = document::import_time_from_engine($doc->modified);
+            $result->title = $doc->title;
+            $result->solr_fileid = $doc->solr_fileid;
+            $result->solr_filecontenthash = $doc->solr_filecontenthash;
+            $result->solr_fileindexedcontent = $doc->solr_fileindexedcontent;
+            $out[] = $result;
+        }
+
+        return $out;
+    }
+
+    /**
+     * Adds a file to the search engine.
+     *
+     * Notes about Solr and Tika indexing. We do not send the mime type, only the filename.
+     * Tika has much better content type detection than Moodle, and we will have many more doc failures
+     * if we try to send mime types.
+     *
+     * @param document $document
+     * @param \stored_file $storedfile
+     * @return void
+     */
+    protected function add_stored_file($document, $storedfile) {
+        $filedoc = $document->export_file_for_engine($storedfile);
+
+        if (!$this->file_is_indexable($storedfile)) {
+            // For files that we don't consider indexable, we will still place a reference in the search engine.
+            $filedoc['solr_fileindexedcontent'] = document::INDEXED_FILE_FALSE;
+            $this->add_solr_document($filedoc);
+            return;
+        }
+
+        $curl = $this->get_curl_object();
+
+        $url = $this->get_connection_url('/update/extract');
+
+        // This will prevent solr from automatically making fields for every tika output.
+        $url->param('uprefix', 'ignored_');
+
+        // These are common fields that matches the standard *_point dynamic field and causes an error.
+        $url->param('fmap.media_white_point', 'ignored_mwp');
+        $url->param('fmap.media_black_point', 'ignored_mbp');
+
+        // Copy each key to the url with literal.
+        // We place in a temp name then copy back to the true field, which prevents errors or Tika overwriting common field names.
+        foreach ($filedoc as $key => $value) {
+            // This will take any fields from tika that match our schema and discard them, so they don't overwrite ours.
+            $url->param('fmap.'.$key, 'ignored_'.$key);
+            // Place data in a tmp field.
+            $url->param('literal.mdltmp_'.$key, $value);
+            // Then move to the final field.
+            $url->param('fmap.mdltmp_'.$key, $key);
+        }
+
+        // This sets the true filename for Tika.
+        $url->param('resource.name', $storedfile->get_filename());
+
+        // A giant block of code that is really just error checking around the curl request.
+        try {
+            // Now actually do the request.
+            $result = $curl->post($url->out(false), array('myfile' => $storedfile));
+
+            $code = $curl->get_errno();
+            $info = $curl->get_info();
+
+            // Now error handling. It is just informational, since we aren't tracking per file/doc results.
+            if ($code != 0) {
+                // This means an internal cURL error occurred error is in result.
+                $message = 'Curl error '.$code.' while indexing file with document id '.$filedoc['id'].': '.$result.'.';
+                debugging($message, DEBUG_DEVELOPER);
+            } else if (isset($info['http_code']) && ($info['http_code'] !== 200)) {
+                // Unexpected HTTP response code.
+                $message = 'Error while indexing file with document id '.$filedoc['id'];
+                // Try to get error message out of msg or title if it exists.
+                if (preg_match('|<str [^>]*name="msg"[^>]*>(.*?)</str>|i', $result, $matches)) {
+                    $message .= ': '.$matches[1];
+                } else if (preg_match('|<title[^>]*>([^>]*)</title>|i', $result, $matches)) {
+                    $message .= ': '.$matches[1];
+                }
+                // This is a common error, happening whenever a file fails to index for any reason, so we will make it quieter.
+                if (CLI_SCRIPT && !PHPUNIT_TEST) {
+                    mtrace($message);
+                }
+            } else {
+                // Check for the expected status field.
+                if (preg_match('|<int [^>]*name="status"[^>]*>(\d*)</int>|i', $result, $matches)) {
+                    // Now check for the expected status of 0, if not, error.
+                    if ((int)$matches[1] !== 0) {
+                        $message = 'Unexpected Solr status code '.(int)$matches[1];
+                        $message .= ' while indexing file with document id '.$filedoc['id'].'.';
+                        debugging($message, DEBUG_DEVELOPER);
+                    } else {
+                        // The document was successfully indexed.
+                        return;
+                    }
+                } else {
+                    // We received an unprocessable response.
+                    $message = 'Unexpected Solr response while indexing file with document id '.$filedoc['id'].': ';
+                    $message .= strtok($result, "\n");
+                    debugging($message, DEBUG_DEVELOPER);
+                }
+            }
+        } catch (\Exception $e) {
+            // There was an error, but we are not tracking per-file success, so we just continue on.
+            debugging('Unknown exception while indexing file "'.$storedfile->get_filename().'".', DEBUG_DEVELOPER);
+        }
+
+        // If we get here, the document was not indexed due to an error. So we will index just the base info without the file.
+        $filedoc['solr_fileindexedcontent'] = document::INDEXED_FILE_ERROR;
+        $this->add_solr_document($filedoc);
+    }
+
+    /**
+     * Checks to see if a passed file is indexable.
+     *
+     * @param \stored_file $file The file to check
+     * @return bool True if the file can be indexed
+     */
+    protected function file_is_indexable($file) {
+        if (!empty($this->config->maxindexfilekb) && ($file->get_filesize() > ($this->config->maxindexfilekb * 1024))) {
+            // The file is too big to index.
+            return false;
+        }
+
+        $mime = $file->get_mimetype();
+
+        if ($mime == 'application/vnd.moodle.backup') {
+            // We don't index Moodle backup files. There is nothing usefully indexable in them.
+            return false;
+        }
+
+        return true;
     }
 
     /**
@@ -360,6 +853,15 @@ class engine extends \core_search\engine {
         return true;
     }
 
+    /**
+     * Return true if file indexing is supported and enabled. False otherwise.
+     *
+     * @return bool
+     */
+    public function file_indexing_enabled() {
+        return (bool)$this->config->fileindexing;
+    }
+
     /**
      * Defragments the index.
      *
@@ -376,7 +878,8 @@ class engine extends \core_search\engine {
      * @return void
      */
     public function delete_by_id($id) {
-        $this->get_search_client()->deleteById($id);
+        // We need to make sure we delete the item and all related files, which can be done with solr_filegroupingid.
+        $this->get_search_client()->deleteByQuery('solr_filegroupingid:' . $id);
         $this->commit();
     }
 
index c68268f..63dfdd2 100644 (file)
@@ -162,7 +162,7 @@ class schema {
             $params = array(
                 'add-field' => array(
                     'name' => $fieldname,
-                    'type' => $data['type'],
+                    'type' => ($data['type'] === 'text' ? 'text_general' : $data['type']),
                     'stored' => $data['stored'],
                     'multiValued' => false,
                     'indexed' => $data['indexed']
@@ -228,7 +228,8 @@ class schema {
                         throw new \moodle_exception('errorcreatingschema', 'search_solr', '',
                             get_string('schemafieldautocreated', 'search_solr', $fieldname));
 
-                    } else if ($results->field->type !== $data['type'] ||
+                    } else if (($results->field->type !== $data['type'] &&
+                                ($data['type'] !== 'text' || $results->field->type !== 'text_general')) ||
                                 $results->field->multiValued !== false ||
                                 $results->field->indexed !== $data['indexed'] ||
                                 $results->field->stored !== $data['stored']) {
index 5633931..ca06662 100644 (file)
  */
 
 $string['connectionerror'] = 'The specified Solr server is not available or the specified index does not exist';
+$string['connectionsettings'] = 'Connection settings';
 $string['errorcreatingschema'] = 'Error creating the Solr schema: {$a}';
 $string['errorvalidatingschema'] = 'Error validating Solr schema, field {$a->fieldname} does not exist. Please <a href="{$a->setupurl}">follow this link</a> to setup the fields required by Moodle.';
 $string['extensionerror'] = 'The Apache Solr PHP extension is not installed. Please check the documentation.';
+$string['fileindexing'] = 'Enable file indexing';
+$string['fileindexing_help'] = 'If your Solr install supports it, this feature allows Moodle to send files to be indexed.';
+$string['fileindexsettings'] = 'File indexing settings';
+$string['maxindexfilekb'] = 'Maximum file size to index (kB)';
+$string['maxindexfilekb_help'] = 'Files larger than this number of kilobytes will be skipped for search indexing. 0 to index files of any size.';
 $string['missingconfig'] = 'Your Apache Solr server is not yet configured in Moodle.';
 $string['multivaluedfield'] = 'Field "{$a}" returned an array instead of a scalar, the field is probably defined in Solr with "Multivalued" to true, this means that Solr autocreated the field for you when you indexed data because you forgot to run search/engine/solr/cli/setup_schema.php. Please delete the current index, create a new one and run setup_schema.php before indexing data in Solr.';
 $string['nodatafromserver'] = 'No data from server';
index 32b409e..56d12c0 100644 (file)
@@ -31,6 +31,8 @@ if ($ADMIN->fulltree) {
             $settings->add(new admin_setting_heading('search_solr_settings', '', get_string('extensionerror', 'search_solr')));
 
         } else {
+            $settings->add(new admin_setting_heading('search_solr_connection',
+                    new lang_string('connectionsettings', 'search_solr'), ''));
             $settings->add(new admin_setting_configtext('search_solr/server_hostname', new lang_string('solrserverhostname', 'search_solr'), new lang_string('solrserverhostname_desc', 'search_solr'), '127.0.0.1', PARAM_TEXT));
             $settings->add(new admin_setting_configtext('search_solr/indexname', new lang_string('solrindexname', 'search_solr'), '', 'moodle', PARAM_TEXT));
             $settings->add(new admin_setting_configcheckbox('search_solr/secure', new lang_string('solrsecuremode', 'search_solr'), '', 0, 1, 0));
@@ -46,6 +48,15 @@ if ($ADMIN->fulltree) {
             $settings->add(new admin_setting_configtext('search_solr/ssl_keypassword', new lang_string('solrsslkeypassword', 'search_solr'), new lang_string('solrsslkeypassword_desc', 'search_solr'), '', PARAM_RAW));
             $settings->add(new admin_setting_configtext('search_solr/ssl_cainfo', new lang_string('solrsslcainfo', 'search_solr'), new lang_string('solrsslcainfo_desc', 'search_solr'), '', PARAM_RAW));
             $settings->add(new admin_setting_configtext('search_solr/ssl_capath', new lang_string('solrsslcapath', 'search_solr'), new lang_string('solrsslcapath_desc', 'search_solr'), '', PARAM_RAW));
+
+            $settings->add(new admin_setting_heading('search_solr_fileindexing',
+                    new lang_string('fileindexsettings', 'search_solr'), ''));
+            $settings->add(new admin_setting_configcheckbox('search_solr/fileindexing',
+                    new lang_string('fileindexing', 'search_solr'),
+                    new lang_string('fileindexing_help', 'search_solr'), 1));
+            $settings->add(new admin_setting_configtext('search_solr/maxindexfilekb',
+                    new lang_string('maxindexfilekb', 'search_solr'),
+                    new lang_string('maxindexfilekb_help', 'search_solr'), '20197152', PARAM_INT));
         }
     }
 }
index 49891a1..f31f6e3 100644 (file)
@@ -99,6 +99,10 @@ class search_solr_engine_testcase extends advanced_testcase {
             set_config('ssl_cainfo', TEST_SEARCH_SOLR_CAINFOCERT, 'search_solr');
         }
 
+        set_config('fileindexing', 1, 'search_solr');
+
+        // We are only test indexing small string files, so setting this as low as we can.
+        set_config('maxindexfilekb', 1, 'search_solr');
 
         // Inject search solr engine into the testable core search as we need to add the mock
         // search component to it.
@@ -213,7 +217,7 @@ class search_solr_engine_testcase extends advanced_testcase {
 
         // Get the doc and insert the default doc.
         $doc = $area->get_document($record);
-        $engine->add_document($doc->export_for_engine());
+        $engine->add_document($doc);
 
         $users = array();
         $users[] = $this->getDataGenerator()->create_user();
@@ -225,9 +229,10 @@ class search_solr_engine_testcase extends advanced_testcase {
 
         // Now add a custom doc for each user.
         foreach ($users as $user) {
+            $doc = $area->get_document($record);
             $doc->set('id', $originalid.'-'.$user->id);
             $doc->set('owneruserid', $user->id);
-            $engine->add_document($doc->export_for_engine());
+            $engine->add_document($doc);
         }
 
         $engine->area_index_complete($area->get_area_id());
@@ -274,4 +279,225 @@ class search_solr_engine_testcase extends advanced_testcase {
         $this->assertEquals(0, $results[0]->get('owneruserid'));
         $this->assertEquals($originalid, $results[0]->get('id'));
     }
+
+    public function test_highlight() {
+        global $PAGE;
+
+        $this->search->index();
+
+        $querydata = new stdClass();
+        $querydata->q = 'message';
+
+        $results = $this->search->search($querydata);
+        $this->assertCount(2, $results);
+
+        $result = reset($results);
+
+        $regex = '|'.\search_solr\engine::HIGHLIGHT_START.'message'.\search_solr\engine::HIGHLIGHT_END.'|';
+        $this->assertRegExp($regex, $result->get('content'));
+
+        $searchrenderer = $PAGE->get_renderer('core_search');
+        $exported = $result->export_for_template($searchrenderer);
+
+        $regex = '|<span class="highlight">message</span>|';
+        $this->assertRegExp($regex, $exported['content']);
+    }
+
+    public function test_index_file() {
+        // Very simple test.
+        $this->search->index();
+        $querydata = new stdClass();
+        $querydata->q = '"File contents"';
+
+        $this->assertCount(2, $this->search->search($querydata));
+    }
+
+    public function test_reindexing_files() {
+        // Get engine and area to work with.
+        $engine = $this->search->get_engine();
+        $areaid = \core_search\manager::generate_areaid('core_mocksearch', 'role_capabilities');
+        $area = \core_search\manager::get_search_area($areaid);
+
+        // Get a single record to make a doc from.
+        $recordset = $area->get_recordset_by_timestamp(0);
+        $record = $recordset->current();
+        $recordset->close();
+
+        $doc = $area->get_document($record);
+
+        // Now we are going to make some files.
+        $fs = get_file_storage();
+        $syscontext = \context_system::instance();
+
+        $files = array();
+        $filerecord = array(
+            'contextid' => $syscontext->id,
+            'component' => 'core',
+            'filearea'  => 'unittest',
+            'itemid'    => 0,
+            'filepath'  => '/',
+        );
+
+        // We make enough so that we pass the 500 files threashold. That is the boundary when getting files.
+        $boundary = 500;
+        $top = (int)($boundary * 1.1);
+        for ($i = 0; $i < $top; $i++) {
+            $filerecord['filename']  = 'searchfile'.$i;
+            $file = $fs->create_file_from_string($filerecord, 'Some FileContents'.$i);
+            $doc->add_stored_file($file);
+            $files[] = $file;
+        }
+
+        // Add the doc with lots of files, then commit.
+        $engine->add_document($doc, true);
+        $engine->area_index_complete($area->get_area_id());
+
+        // Indexes we are going to check. 0 means we will delete, 1 means we will keep.
+        $checkfiles = array(
+            0 => 0,                        // Check the begining of the set.
+            1 => 1,
+            2 => 0,
+            ($top - 3) => 0,               // Check the end of the set.
+            ($top - 2) => 1,
+            ($top - 1) => 0,
+            ($boundary - 2) => 0,          // Check at the boundary between fetch groups.
+            ($boundary - 1) => 0,
+            $boundary => 0,
+            ($boundary + 1) => 0,
+            ((int)($boundary * 0.5)) => 1, // Make sure we keep some middle ones.
+            ((int)($boundary * 1.05)) => 1
+        );
+
+        $querydata = new stdClass();
+
+        // First, check that all the files are currently there.
+        foreach ($checkfiles as $key => $unused) {
+            $querydata->q = 'FileContents'.$key;
+            $this->assertCount(1, $this->search->search($querydata));
+            $querydata->q = 'searchfile'.$key;
+            $this->assertCount(1, $this->search->search($querydata));
+        }
+
+        // Remove the files we want removed from the files array.
+        foreach ($checkfiles as $key => $keep) {
+            if (!$keep) {
+                unset($files[$key]);
+            }
+        }
+
+        // And make us a new file to add.
+        $filerecord['filename']  = 'searchfileNew';
+        $files[] = $fs->create_file_from_string($filerecord, 'Some FileContentsNew');
+        $checkfiles['New'] = 1;
+
+        $doc = $area->get_document($record);
+        foreach($files as $file) {
+            $doc->add_stored_file($file);
+        }
+
+        // Reindex the document with the changed files.
+        $engine->add_document($doc, true);
+        $engine->area_index_complete($area->get_area_id());
+        cache_helper::purge_by_definition('core', 'search_results');
+
+        // Go through our check array, and see if the file is there or not.
+        foreach ($checkfiles as $key => $keep) {
+            $querydata->q = 'FileContents'.$key;
+            $this->assertCount($keep, $this->search->search($querydata));
+            $querydata->q = 'searchfile'.$key;
+            $this->assertCount($keep, $this->search->search($querydata));
+        }
+
+        // Now check that we get one result when we search from something in all of them.
+        $querydata->q = 'Some';
+        $this->assertCount(1, $this->search->search($querydata));
+    }
+
+    public function test_index_filtered_file() {
+        // Get engine and area to work with.
+        $engine = $this->search->get_engine();
+        $areaid = \core_search\manager::generate_areaid('core_mocksearch', 'role_capabilities');
+        $area = \core_search\manager::get_search_area($areaid);
+
+        // Get a single record to make a doc from.
+        $recordset = $area->get_recordset_by_timestamp(0);
+        $record = $recordset->current();
+        $recordset->close();
+
+        $doc = $area->get_document($record);
+
+        // Now we are going to make some files.
+        $fs = get_file_storage();
+        $syscontext = \context_system::instance();
+
+        $files = array();
+        $filerecord = array(
+            'contextid' => $syscontext->id,
+            'component' => 'core',
+            'filearea'  => 'unittest',
+            'itemid'    => 0,
+            'filepath'  => '/',
+            'filename'  => 'largefile'
+        );
+
+        // We need to make a file greater than 1kB in size, which is the lowest filter size.
+        $contents = 'Some LargeFindContent to find.';
+        for ($i = 0; $i < 200; $i++) {
+            $contents .= ' The quick brown fox jumps over the lazy dog.';
+        }
+
+        $this->assertGreaterThan(1024, strlen($contents));
+
+        $file = $fs->create_file_from_string($filerecord, $contents);
+        $doc->add_stored_file($file);
+
+        $filerecord['filename'] = 'smallfile';
+        $file = $fs->create_file_from_string($filerecord, 'Some SmallFindContent to find.');
+        $doc->add_stored_file($file);
+
+        $engine->add_document($doc, true);
+        $engine->area_index_complete($area->get_area_id());
+
+        $querydata = new stdClass();
+        // We shouldn't be able to find the large file contents.
+        $querydata->q = 'LargeFindContent';
+        $this->assertCount(0, $this->search->search($querydata));
+
+        // But we should be able to find the filename.
+        $querydata->q = 'largefile';
+        $this->assertCount(1, $this->search->search($querydata));
+
+        // We should be able to find the small file contents.
+        $querydata->q = 'SmallFindContent';
+        $this->assertCount(1, $this->search->search($querydata));
+
+        // And we should be able to find the filename.
+        $querydata->q = 'smallfile';
+        $this->assertCount(1, $this->search->search($querydata));
+    }
+
+    public function test_delete_by_id() {
+        // First get files in the index.
+        $this->search->index();
+        $engine = $this->search->get_engine();
+
+        $querydata = new stdClass();
+
+        // Then search to make sure they are there.
+        $querydata->q = '"File contents"';
+        $results = $this->search->search($querydata);
+        $this->assertCount(2, $results);
+
+        $first = reset($results);
+        $deleteid = $first->get('id');
+
+        $engine->delete_by_id($deleteid);
+        cache_helper::purge_by_definition('core', 'search_results');
+
+        // Check that we don't get a result for it anymore.
+        $results = $this->search->search($querydata);
+        $this->assertCount(1, $results);
+        $result = reset($results);
+        $this->assertNotEquals($deleteid, $result->get('id'));
+    }
 }
index 6d39ae8..f28c00a 100644 (file)
@@ -38,6 +38,9 @@
     * userfullname
     * description1
     * description2
+    * filename
+    * multiplefiles
+    * filenames
 
     Example context (json):
     {
         "contexturl": "https://example.com/mod/example/view.php?id=2"
         "userurl": "https://example.com/user/profile.php?id=3",
         "userfullname": "Example User Full Name",
+        "multiplefiles": true,
+        "filenames":
+        {
+            "file1.txt",
+            "file2.txt"
+        }
     }
 }}
 <div class="result">
     <h4 class="result-title">
-        <a href="{{{docurl}}}">{{title}}</a>
+        <a href="{{{docurl}}}">{{{title}}}</a>
     </h4>
     {{#content}}
         <div class="result-content">{{{content}}}</div>
     {{#description2}}
         <div class="result-content">{{{description2}}}</div>
     {{/description2}}
+    {{#filename}}
+        <div class="result-content-filename">
+            {{#str}}matchingfile, search, {{filename}}{{/str}}
+        </div>
+    {{/filename}}
+    {{#multiplefiles}}
+        <div class="result-content-filenames">
+            {{#str}}matchingfiles, search{{/str}}<br>
+            <ul class="list">
+            {{#filenames}}
+                <li><span class="filename">{{.}}</span></li>
+            {{/filenames}}
+            </ul>
+        </div>
+    {{/multiplefiles}}
     <div class="result-context-info">
         <a href="{{{contexturl}}}">{{#str}}viewresultincontext, search{{/str}}</a> -
         <a href="{{{courseurl}}}">{{#str}}incourse, search, {{coursefullname}}{{/str}}</a>
index de7febb..7528e01 100644 (file)
@@ -44,7 +44,7 @@ class role_capabilities extends \core_search\area\base {
         return $DB->get_recordset_sql("SELECT id, contextid, roleid, capability FROM {role_capabilities} where timemodified >= ? and capability = ?", array($modifiedfrom, 'moodle/course:renameroles'));
     }
 
-    public function get_document($record) {
+    public function get_document($record, $options = array()) {
         global $USER;
 
         // Prepare associative array with data from DB.
@@ -52,7 +52,6 @@ class role_capabilities extends \core_search\area\base {
         $doc->set('title', $record->capability . ' roleid ' . $record->roleid);
         $doc->set('content', $record->capability . ' roleid ' . $record->roleid . ' message');
         $doc->set('contextid', $record->contextid);
-        $doc->set('type', \core_search\manager::TYPE_TEXT);
         $doc->set('courseid', SITEID);
         $doc->set('userid', $USER->id);
         $doc->set('owneruserid', \core_search\manager::NO_OWNER_ID);
@@ -61,6 +60,30 @@ class role_capabilities extends \core_search\area\base {
         return $doc;
     }
 
+    public function attach_files($document) {
+        global $CFG;
+
+        // Add the searchable file fixture.
+        $syscontext = \context_system::instance();
+        $filerecord = array(
+            'contextid' => $syscontext->id,
+            'component' => 'core',
+            'filearea'  => 'unittest',
+            'itemid'    => 0,
+            'filepath'  => '/',
+            'filename'  => 'searchfile'.$document->get('itemid').'.txt',
+        );
+
+        $fs = get_file_storage();
+        $file = $fs->create_file_from_string($filerecord, 'File contents');
+
+        $document->add_stored_file($file);
+    }
+
+    public function uses_file_indexing() {
+        return true;
+    }
+
     public function check_access($id) {
         return \core_search\manager::ACCESS_GRANTED;
     }
index 0021d95..3859354 100644 (file)
@@ -37,7 +37,7 @@ class engine extends \core_search\engine {
         return true;
     }
 
-    public function add_document($doc) {
+    public function add_document($document, $fileindexing = false) {
         // No need to implement.
     }
 
index baae4cd..069500b 100644 (file)
@@ -12,3 +12,6 @@
     margin: 7px 0;
 }
 
+.search-results .result .filename {
+    font-style: italic;
+}
index 2a382f5..e9c1d06 100644 (file)
     margin: 7px 0;
 }
 
+.search-results .result .filename {
+    font-style: italic;
+}
+
 .search-input-wrapper {
     margin: 0 5px 0 2px;
     overflow: hidden;
index acffc43..be49909 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-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}.content-only #region-main-box,.content-only #region-main{width:100%}.empty-region-side-pre.used-region-side-post #region-main{width:100%}.empty-region-side-post.used-region-side-pre #region-main-box{width:100%}.jsenabled.docked-region-side-pre.empty-region-side-pre.used-region-side-post #region-main{width:100%}.jsenabled.docked-region-side-post.empty-region-side-post.used-region-side-pre #region-main-box{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.46808511%;*width:74.41489362%}.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.40425532%;*width:23.35106383%}.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{list-style:none;margin:0;padding:0}.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}.pagelayout-mydashboard.jsenabled .collapsibleregioncaption{cursor:pointer}.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{background-color:#FFEE69;border:1px solid #A6982B;border-top-color:#D4C237;color:#000000;left:0;padding:2px 5px;position:relative;top:0;z-index:1}.clearer{background:transparent;border-width:0;clear:both;display:block;height:1px;margin:0;padding:0}.bold,.warning,.errorbox .title,.pagingbar .title,.pagingbar .thispage{font-weight:bold}img.resize{height:1em;width:1em}.block img.resize,.breadcrumb img.resize{height:.9em;width:.8em}img.icon{height:16px;vertical-align:text-bottom;width:16px;padding-right:6px}.dir-rtl img.icon{padding-left:6px;padding-right:0}img.iconsmall{height:12px;margin-right:3px;vertical-align:middle;width:12px}img.iconhelp,.helplink img{height:16px;padding-left:3px;vertical-align:text-bottom;width:16px}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{vertical-align:middle;padding:4px}.dir-rtl img.iconhelp,.dir-rtl .helplink img{padding-right:3px;padding-left:0}img.iconlarge{height:24px;width:24px;vertical-align:middle}img.iconsort{vertical-align:text-bottom;padding-left:.3em;margin-bottom:.15em}.dir-rtl img.iconsort{padding-right:.3em;padding-left:0}img.icontoggle{height:17px;vertical-align:middle;width:50px}img.iconkbhelp{height:17px;width:49px}img.icon-pre,.dir-rtl img.icon-post{padding-right:3px;padding-left:0}img.icon-post,.dir-rtl img.icon-pre{padding-left:3px;padding-right:0}.boxaligncenter{margin-left:auto;margin-right:auto}.boxalignright{margin-left:auto;margin-right:0}.boxalignleft{margin-left:0;margin-right:auto}.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-left:auto;margin-right:auto}table.flexible .emptyrow{display:none}img.emoticon{vertical-align:middle;width:15px;height:15px}form.popupform,form.popupform div{display:inline}.arrow_button input{overflow:hidden}.action-icon img.smallicon{vertical-align:text-bottom;margin:0 .3em}.no-overflow{overflow:auto;padding-bottom:1px}.pagelayout-report .no-overflow{overflow:visible}.no-overflow>.generaltable{margin-bottom:0}.accesshide{position:absolute;left:-10000px;font-weight:normal;font-size:1em}.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{text-align:right;padding-right:4px}.blog_entry .tags{margin-top:15px}.blog_entry .tags .action-icon img.smallicon{height:16px;width: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{margin:0;padding:0;width:90%}#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{margin-top:3px;margin-bottom:3px;display:inline-block}.groupselector label{display:inline-block}.loginbox{margin:15px;overflow:visible}.loginbox.twocolumns{margin:15px}.loginbox h2,.loginbox .subcontent{margin:5px;padding:10px;text-align:center}.loginbox .loginpanel .desc{margin:0;padding:0;margin-bottom:5px;margin-top:15px}.loginbox .signuppanel .subcontent{text-align:left}.dir-rtl .loginbox .signuppanel .subcontent{text-align:right}.loginbox .loginsub{margin-left:0;margin-right: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;text-align:right;width:49%;white-space:nowrap}.loginbox .loginform .form-input{float:right;width:50%}.loginbox .loginform .form-input input{width:6em}.loginbox .signupform{margin-top:1em;text-align:center}.loginbox.twocolumns .loginpanel,.loginbox.twocolumns .signuppanel{width:48%;border:0;margin:0;padding:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;display:block;float:left;margin-left:2.76243%;min-height:30px;margin-bottom:-2000px;padding-bottom:2000px}.dir-rtl .loginbox.twocolumns .loginpanel,.dir-rtl .loginbox.twocolumns .signuppanel{float:right}.loginbox .potentialidp .smallicon{vertical-align:text-bottom;margin:0 .3em}.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{font-size:12px;display:none;margin:0;padding:0}.comment-ctrl h5{margin:0;padding:5px}.comment-area{max-width:400px;padding:5px}.comment-area textarea{width:100%;overflow:auto}.comment-area textarea.fullwidth{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.comment-area .fd{text-align:right}.comment-meta span{color:gray}.comment-link img{vertical-align:text-bottom}.comment-list{font-size:11px;overflow:auto;list-style:none;padding:0;margin:0}.comment-list li{margin:2px;list-style:none;margin-bottom:5px;clear:both;padding:.3em;position:relative}.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{width:20px;float:left}.dir-rtl .comment-message .picture{float:right}.comment-message .text{margin:0;padding: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;left:0;right:auto;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}img.user-image{height:100px;width:100px}#tag-search-box{text-align:center;margin:10px auto}.path-tag .tag-index-items .tagarea{border:1px solid #E3E3E3;border-radius:4px;padding:10px;margin-top:10px}.path-tag .tag-index-items .tagarea h3{display:block;padding:3px 0 10px 0;margin:0;font-size:1.1em;font-weight:bold;line-height:20px;color:#999;text-shadow:0 1px 0 rgba(255,255,255,0.5);text-transform:uppercase;word-wrap:break-word;border-bottom:solid 1px #E3E3E3;margin-bottom:10px}.path-tag .tagarea .controls,.path-tag .tagarea .taggeditems{*zoom:1}.path-tag .tagarea .controls:before,.path-tag .tagarea .taggeditems:before,.path-tag .tagarea .controls:after,.path-tag .tagarea .taggeditems:after{display:table;content:"";line-height:0}.path-tag .tagarea .controls:after,.path-tag .tagarea .taggeditems:after{clear:both}.path-tag .tagarea .controls,.path-tag .tag-backtoallitems{text-align:center}.path-tag .tagarea .controls .gotopage.nextpage{float:right}.path-tag .tagarea .controls .gotopage.prevpage{float:left}.path-tag .tagarea .controls .exclusivemode{display:inline-block}.dir-rtl.path-tag .tagarea .controls .gotopage.nextpage{float:left}.dir-rtl.path-tag .tagarea .controls .gotopage.prevpage{float:right}.path-tag .tagarea .controls.controls-bottom{margin-top:5px}.path-tag .tagarea .controls .gotopage.nextpage::after{padding-right:5px;padding-left:5px;content:"»"}.path-tag .tagarea .controls .gotopage.prevpage::before{padding-right:5px;padding-left:5px;content:"«"}span.flagged-tag,tr.flagged-tag,span.flagged-tag a,tr.flagged-tag a{color:#b94a48}.tag-management-table td,.tag-management-table th{vertical-align:middle;padding:4px}.tag-management-table .inplaceeditable.inplaceeditingon input{width:150px}.path-tag .tag-relatedtags{padding-top:10px}.path-tag .tag-management-box{text-align:right}.path-tag .tag-index-toc{padding:10px;text-align:center}.path-tag .tag-index-toc li,.path-tag .tag-management-box li{margin-left:5px;margin-right:5px}.path-tag .tag-management-box li a.edittag{background-image:url([[pix:moodle|i/settings]])}.path-tag .tag-management-box li a.flagasinappropriate{background-image:url([[pix:moodle|i/flagged]])}.path-tag .tag-management-box li a.removefrommyinterests{background-image:url([[pix:moodle|t/delete]])}.path-tag .tag-management-box li a.addtomyinterests{background-image:url([[pix:moodle|t/add]])}.path-tag .tag-management-box li a{background-repeat:no-repeat;background-position:left;padding-left:17px}.tag_feed.media-list .media .itemimage{float:left}.dir-rtl .tag_feed.media-list .media .itemimage{float:right}.tag_feed.media-list .media .itemimage img{height:35px;width:35px}.tag_feed.media-list .media .media-body{padding-right:10px;padding-left:10px}.tag_feed .media .muted a{color:#999}.tag_cloud{text-align:center}.tag_cloud .inline-list li{padding:0 .2em}.tag_cloud .tag_overflow{margin-top:1em;font-style:italic}.tag_cloud .s20{font-size:2.7em}.tag_cloud .s19{font-size:2.6em}.tag_cloud .s18{font-size:2.5em}.tag_cloud .s17{font-size:2.4em}.tag_cloud .s16{font-size:2.3em}.tag_cloud .s15{font-size:2.2em}.tag_cloud .s14{font-size:2.1em}.tag_cloud .s13{font-size:2em}.tag_cloud .s12{font-size:1.9em}.tag_cloud .s11{font-size:1.8em}.tag_cloud .s10{font-size:1.7em}.tag_cloud .s9{font-size:1.6em}.tag_cloud .s8{font-size:1.5em}.tag_cloud .s7{font-size:1.4em}.tag_cloud .s6{font-size:1.3em}.tag_cloud .s5{font-size:1.2em}.tag_cloud .s4{font-size:1.1em}.tag_cloud .s3{font-size:1em}.tag_cloud .s2{font-size:.9em}.tag_cloud .s1{font-size:.8em}.tag_cloud .s0{font-size:.7em}.tag_list ul{display:inline}.tag_list.hideoverlimit .overlimit{display:none}.tag_list .tagmorelink{display:none}.tag_list.hideoverlimit .tagmorelink{display:inline}.tag_list.hideoverlimit .taglesslink{display:none}#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{border:1px solid #000;background-color:#FFF;display:none}.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-repeat:no-repeat;background-position:100%}.smartselect.spanningmenu .smartselect_submenu{position:absolute;top:-1px;left:100%}.smartselect.spanningmenu .smartselect_submenu a{white-space:nowrap;padding-right:16px}.smartselect.spanningmenu .smartselect_menu_content ul li a.selectable:hover{text-decoration:underline}.smartselect.compactmenu .smartselect_submenu{position:relative;margin:2px -3px;margin-left:10px;display:none;border-width:0;z-index:1010}.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{padding:0;height:41px}.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>a:hover,.userenrolment .col_group .addgroup>a: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{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);border-color:#e3e3e3;padding:9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;display:inline-block}#page-enrol-users #filterform blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}#page-enrol-users #filterform .fitem{display:inline-block;line-height:40px;margin-right:.3em;white-space:nowrap}#page-enrol-users #filterform .fitem label{display:inline;line-height:20px;padding-right:.3em}#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;clear:none;width:auto;margin:0}#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}#page-enrol-users .user-enroller-panel .uep-search-results .cohort .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-left:0;margin-right:1em}.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}#page-enrol-users .enrol-users-page-action input{margin-left:0}.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;margin-right:1%}.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{background-color:#CCC;position:absolute;top:0;left:0;width:100%;height:100%;text-align:center}.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{width:450px}.mod-indent-16{width:480px}.mod-indent-huge{width:480px}.resourcecontent .mediaplugin_mp3 object{height:25px;width:600px}.resourcecontent audio.mediaplugin_html5audio{width:600px}.resourceimage{max-width:100%}.mediaplugin_mp3 object{height:15px;width:300px}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{padding:0;margin:0;min-width:0;background:none}.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;background-image:url([[pix:theme|yui2-treeview-sprite-rtl]]);background-repeat:no-repeat;cursor:pointer}.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{white-space:nowrap;text-align:center}.initialbar a,.initialbar strong{padding-left:3px;padding-right: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{left:0;top:0;right:0;bottom:-50px;position:fixed}.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{padding:0;margin:0;background:none;border:none;z-index:600;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{margin:0;padding:5px;font-size:12px;font-weight:normal;letter-spacing:1px;color:#333;text-align:center;text-shadow:1px 1px 1px #fff;-webkit-border-radius:10px 10px 0 0;-moz-border-radius:10px 10px 0 0;border-radius:10px 10px 0 0;border-bottom:1px solid #bbb;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;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffcccccc', GradientType=0);filter:0}.moodle-dialogue-base .moodle-dialogue-wrap .moodle-dialogue-hd h1{margin:0;padding:0;display:inline;font-size:100%;font-weight:bold}.moodle-dialogue-base .moodle-dialogue-wrap .moodle-dialogue-hd .yui3-widget-buttons{padding:5px}.moodle-dialogue-base .closebutton{width:25px;height:15px;float:right;vertical-align:middle;display:inline-block;cursor:pointer;padding:0;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{left:0;right:auto}.moodle-dialogue-base .moodle-dialogue .moodle-dialogue-bd{padding:1em;line-height:2em;color:#555;font-size:12px}.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{overflow:auto;position:absolute;top:0;bottom:50px;left:0;right:0;margin:0;border:0}.moodle-dialogue-base .moodle-dialogue-fullscreen .moodle-dialogue-hd,.moodle-dialogue-base .moodle-dialogue-fullscreen .moodle-dialogue-wrap{border-radius:0}.moodle-dialogue-confirm .confirmation-dialogue{text-align:center}.moodle-dialogue-confirm .confirmation-dialogue input{text-align:center}.moodle-dialogue-exception .moodle-exception-message{text-align:center}.moodle-dialogue-exception .moodle-exception-param label{font-weight:bold}.moodle-dialogue-exception .param-stacktrace label{background-color:#EEE;border:1px solid #ccc;border-bottom-width:0}.moodle-dialogue-exception .param-stacktrace pre{border:1px solid #ccc;background-color:#fff}.moodle-dialogue-exception .param-stacktrace .stacktrace-file{color:navy;font-size:11.9px}.moodle-dialogue-exception .param-stacktrace .stacktrace-line{color:#b94a48;font-size:11.9px}.moodle-dialogue-exception .param-stacktrace .stacktrace-call{color:#333;font-size:90%;border-bottom:1px solid #eee}.moodle-dialogue-base .moodle-dialogue .moodle-dialogue-content .moodle-dialogue-ft{padding:0;margin:.7em 1em;text-align:right;background-color:#FFF;font-size:12px}.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;margin:0;padding:4px 1em}.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-left:25px;margin-bottom:4px;padding-bottom:4px}.moodle-dialogue .moodle-dialogue-bd .content-lightbox{opacity:.75;filter:alpha(opacity=75);width:100%;height:100%;top:0;left:0;background-color:white;text-align:center;padding:10% 0}.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;-moz-border-radius-bottomright:10px;border-bottom-right-radius:10px;-webkit-border-bottom-left-radius:10px;-moz-border-radius-bottomleft:10px;border-bottom-left-radius: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 #BBBBBB}.jschooser .choosercontainer #chooseform .alloptions{overflow-x:hidden;overflow-y:auto;max-width:20.3em;-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}.jschooser .choosercontainer #chooseform .alloptions .option input[type=radio]{display:inline-block}.jschooser .choosercontainer #chooseform .alloptions .option .modicon{display:inline-block}.jschooser .choosercontainer #chooseform .alloptions .option .typename{display:inline-block;width:65%}.dir-rtl.jschooser .choosercontainer #chooseform .alloptions{max-width:18.3em}.choosercontainer #chooseform .moduletypetitle,.choosercontainer #chooseform .option,.choosercontainer #chooseform .nonoption{margin-bottom:0;padding:0 1.6em 0 1.6em}.choosercontainer #chooseform .moduletypetitle{text-transform:uppercase;padding-top:1.2em;padding-bottom:.4em}.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}.chooserdialogue-course-modchooser .choosercontainer #chooseform .option span.modicon img.icon,.chooserdialogue-course-modchooser .choosercontainer #chooseform .nonoption span.modicon img.icon{height:24px;width: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 #FFFFFF}.choosercontainer #chooseform .nonoption{padding-left:2.7em;padding-top:.3em;padding-bottom:.1em}.dir-rtl .choosercontainer #chooseform .nonoption{padding-right:2.7em;padding-left:0}.choosercontainer #chooseform .subtype{margin-bottom:0;padding:0 1.6em 0 3.2em}.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{display:none;position:absolute;top:0;right:0;bottom:0;left:20.3em;margin:0;padding:1.6em;background-color:#fff;overflow-x:hidden;overflow-y:auto;line-height:2em}.dir-rtl.jschooser .choosercontainer #chooseform .instruction,.dir-rtl.jschooser .choosercontainer #chooseform .typesummary{left:0;right:18.5em;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-bottom:25px;padding-right:10px}.formlistinginputradio{float:left}.formlistingmain{min-height:225px}.formlisting{position:relative;margin:15px 0;padding:1px 19px 14px;background-color:white;border:1px solid #DDD;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.formlistingmore{position:absolute;cursor:pointer;bottom:-1px;right:-1px;padding:3px 7px;font-size:12px;font-weight:bold;background-color:whiteSmoke;border:1px solid #ddd;color:#9DA0A4;-webkit-border-radius:4px 0 4px 0;-moz-border-radius:4px 0 4px 0;border-radius:4px 0 4px 0}.formlistingall{margin:15px 0;padding:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.formlistingrow{cursor:pointer;border-bottom:1px solid;border-color:#E1E1E8;border-left:1px solid #E1E1E8;border-right:1px solid #E1E1E8;background-color:#F7F7F9;-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px;padding:6px;top:50%;left:50%;min-height:34px;float:left;width:150px}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;-moz-border-radius-topleft:4px;border-top-left-radius: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;-moz-border-radius-topright:4px;border-top-right-radius: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;-moz-border-radius-bottomleft:4px;border-bottom-left-radius: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;-moz-border-radius-bottomright:4px;border-bottom-right-radius:4px}table.collection tfoot+tbody:last-child tr:last-child td:first-child{-webkit-border-bottom-left-radius:0;-moz-border-radius-bottomleft:0;border-bottom-left-radius:0}table.collection tfoot+tbody:last-child tr:last-child td:last-child{-webkit-border-bottom-right-radius:0;-moz-border-radius-bottomright:0;border-bottom-right-radius: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;-moz-border-radius-topleft:4px;border-top-left-radius: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;-moz-border-radius-topright:4px;border-top-right-radius: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}.dir-rtl table.collection .name,.dir-rtl table.collection .criteria,.dir-rtl table.collection .description{text-align:right}table.collection .actions{width:11em;text-align:center;vertical-align:middle}a.criteria-action{padding:0 3px;float:right}div.criteria-description{padding:10px 15px;margin:5px 0;background:none repeat scroll 0 0 #f9f9f9;border:1px solid #EEE}ul.badges{margin:0;list-style:none}.badges li{position:relative;display:inline-block;padding-top:1em;text-align:center;vertical-align:top;width:150px}.badges li .badge-name{display:block;padding:5px}.badges li>img{position:absolute}.badges li .badge-image{width:100px;height:100px;left:10px;top:0;z-index:1}.badges li .badge-actions{position:relative}.badges li .expireimage{width:100px;height:100px;left:25px;top:0;position:absolute;z-index:10;opacity:.85}#badge-image{background-color:transparent;padding:0;position:relative;min-width:100px;width:20%;display:inline-block;vertical-align:top;margin-top:17px}#badge-image .expireimage{width:100px;height:100px;left:0;top:0;opacity:.85;filter:alpha(opacity=85);position:absolute;z-index:10}#badge-image .singlebutton{padding-top:5px}#badge-image .singlebutton input{margin-left:0}.dir-rtl #badge-image{float:right}.dir-rtl #badge-image .expireimage{left:41px}#badge-details{display:inline-block;width:79%}#badge-overview dl,#badge-details dl{margin:0}#badge-overview dl dt,#badge-details dl dt,#badge-overview dl dd,#badge-details dl dd{vertical-align:top;padding:3px 0}#badge-overview dl dt,#badge-details dl dt{clear:both;display:inline-block;width:20%;min-width:100px}#badge-overview dl dd,#badge-details dl dd{display:inline-block;width:79%;margin-left:1%}.badge-profile{vertical-align:top}.connected{color:#468847}.notconnected{color:#b94a48}.connecting{color:#8a6d3b}#page-badges-award .recipienttable tr td{vertical-align:top}#page-badges-award .recipienttable tr td.actions .actionbutton{margin:.3em 0;padding:.5em 0;width:100%}#page-badges-award .recipienttable tr td.existing,#page-badges-award .recipienttable tr td.potential{width:42%}#issued-badge-table .activatebadge{display:inline-block}.statusbox.active{background-color:#dff0d8}.statusbox.inactive{background-color:#fcf8e3}.statusbox{text-align:center;margin-bottom:5px;padding:5px}.statusbox .activatebadge{display:inline-block}.statusbox .activatebadge input[type=submit]{margin:3px}.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;margin:0;padding: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}.page-context-header{overflow:hidden}.page-context-header .page-header-image,.page-context-header .page-header-headings{display:block;position:relative}.page-context-header .page-header-image{margin-bottom:1em}.page-context-header .page-header-headings{margin-top:30px;margin-bottom:10px}.page-context-header .page-header-headings h1{display:block}.page-context-header .page-header-headings,.page-context-header .header-button-group{position:relative;line-height:24px;vertical-align:middle}.page-context-header .header-button-group{display:block}.page-context-header .header-button-group a{position:relative;top:-0.4em}.dir-ltr .page-context-header .page-header-image{float:left;margin-right:1em}.dir-ltr .page-context-header .header-button-group{float:left}.dir-rtl .page-context-header .page-header-image{float:right;margin-left:1em}.dir-rtl .page-context-header .header-button-group{float:right}.moodle-actionmenu,.moodle-actionmenu>ul,.moodle-actionmenu>ul>li{display:inline-block}.moodle-actionmenu ul{padding:0;margin:0;list-style-type:none}.section_action_menu .moodle-actionmenu ul.menubar{margin:0}.section_action_menu .moodle-actionmenu ul.menu{margin:0 10px 10px 0}.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;margin-left:4px;padding-left:4px;padding-right:4px}.jsenabled .moodle-actionmenu[data-enhance] .toggle-display.textmenu .iconsmall,.jsenabled .moodle-actionmenu[data-enhance] .toggle-display.textmenu .smallicon{margin:4px 4px 4px 0;padding:8px 4px 0 2px;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-left:initial;margin-right:4px}.jsenabled.dir-rtl .moodle-actionmenu[data-enhance] .toggle-display.textmenu .caret{margin-left:initial;margin-right:2px}.moodle-actionmenu[data-enhanced].show{position:relative}.moodle-actionmenu[data-enhanced].show .menu{display:block;position:absolute;text-align:left;background-color:#fff;border:1px solid rgba(0,0,0,0.2);z-index:1000;-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;color:#333;padding:2px 1em 2px 28px}.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;-moz-border-radius-topright:4px;border-top-right-radius:4px;-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px}.moodle-actionmenu[data-enhanced].show .menu a:last-child{-webkit-border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;border-bottom-right-radius:4px;-webkit-border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;border-bottom-left-radius: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,.moodle-actionmenu[data-enhanced].show .menu .smallicon{margin:4px 4px 4px -24px;padding:4px}.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{bottom:100%;right: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{bottom:100%;right:0}.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{bottom:100%;right: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{bottom:100%;right:0;margin-bottom:4px}.moodle-actionmenu[data-enhanced].show.nowrap-items .menu>li{white-space:nowrap}.block .moodle-actionmenu{text-align:right}.dir-rtl .moodle-actionmenu[data-enhanced].show .menu{text-align:right;left:0;right:auto}.dir-rtl .moodle-actionmenu[data-enhanced].show .menu a{padding:2px 28px 2px 1em}.dir-rtl .moodle-actionmenu[data-enhanced].show .menu .iconsmall,.dir-rtl .moodle-actionmenu[data-enhanced].show .menu .smallicon{margin-right:-24px;margin-left:4px}.dir-rtl .moodle-actionmenu[data-enhanced].show .menu.align-tl-bl{left:auto;right:0}.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{left:auto;right:0}.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{left:auto;right:100%}.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{left:auto;right:100%}.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{left:auto;right:0}.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{left:auto;right:0}.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{left:auto;right:100%}.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{left:auto;right:100%}.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{text-decoration:none;cursor:default;font-style:italic;color:#808080}body.lockscroll{height:100%;overflow:hidden}.dir-rtl ul{margin-left:0;margin-right:25px}.progressbar_container{max-width:500px;margin:0 auto}.ie10 .yui3-calendar-header-label{display:inline-block}dd:before,dd:after{display:block;content:" "}dd:after{clear:both}.nav-tabs>.active>a[href],.nav-tabs>.active>a[href]:hover,.nav-tabs>.active>a[href]:focus{cursor:pointer}span.inplaceeditable.inplaceeditingon{position:relative}span.inplaceeditable.inplaceeditingon span.editinstructions{margin-top:-30px;font-weight:normal;margin-right:-300px;margin-left:0}.dir-rtl span.inplaceeditable.inplaceeditingon span.editinstructions{margin-left:-300px;margin-right:0}.inplaceeditable.inplaceeditingon{position:relative}.inplaceeditable.inplaceeditingon .editinstructions{margin-top:-30px;font-weight:normal;margin-right:-300px;margin-left:0}.inplaceeditable.inplaceeditingon input{width:330px;height:16px;vertical-align:text-bottom;margin-bottom:0}.inplaceeditable.inplaceeditingon select{margin-bottom:0}.inplaceeditable .quickediticon img{opacity:.2}.inplaceeditable .quickeditlink{color:inherit;text-decoration:inherit}.inplaceeditable:hover .quickeditlink .quickediticon img,.inplaceeditable .quickeditlink:focus .quickediticon img{opacity:1}.inplaceeditable.inplaceeditable-toggle .quickediticon{display:none}.dir-rtl .inplaceeditable.inplaceeditingon .editinstructions{margin-left:-300px;margin-right:0}h3.sectionname .inplaceeditable.inplaceeditingon .editinstructions{margin-top:-20px}.formtable tbody th{font-weight:normal;text-align:right}.path-admin #assignrole{width:60%;margin-left:auto;margin-right:auto}.path-admin .admintable .leftalign{text-align:left}.dir-rtl.path-admin .admintable .leftalign{text-align:right}.environmenttable p.warn{background-color:#fcf8e3;color:#8a6d3b}.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;vertical-align:baseline;white-space:nowrap;text-shadow:0 -1px 0 rgba(0,0,0,0.25);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-left:10px;padding-right: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;vertical-align:baseline;white-space:nowrap;text-shadow:0 -1px 0 rgba(0,0,0,0.25);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:#8a6d3b}.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:#8a6d3b}#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{margin:0;padding:.3em}#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{text-align:center;margin-bottom:1em}.path-admin-roles .capabilitysearchui{text-align:left;margin-left:auto;margin-right:auto}#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{margin:auto;padding:8px 35px 8px 14px;margin-bottom:20px;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;color:#8a6d3b;width:60%;min-width:220px}#page-admin-index .adminerror{background-color:#f2dede;border-color:#eed3d7;color:#b94a48}#page-admin-index .releasenoteslink{background-color:#d9edf7;border-color:#bce8f1;color:#3a87ad}#page-admin-index .adminwarning.availableupdatesinfo .moodleupdateinfo span{display:block}#page-admin-index .updateplugin div{margin-bottom:.5em}#page-admin-index .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{text-align:center;margin-bottom:1em}#page-admin-report-courseoverview-index .graph{text-align:center;margin-bottom:1em}#page-admin-lang .translator{border-width:1px;border-style:solid}.path-admin .roleassigntable{width:100%}.path-admin .roleassigntable td{vertical-align:top;padding:.2em .3em}.path-admin .roleassigntable p{text-align:left;margin:.2em 0}.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{text-align:left;font-weight:normal}.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{text-align:center;padding:0;min-width:3.5em}.path-admin .rolecap .cap-name,.path-admin .rolecap .note{display:block;font-size:10.5px;white-space:nowrap;font-weight:normal}.path-admin .rolecap label{display:block;text-align:center;padding:.5em;margin:0}.plugincheckwrapper{width:100%}.environmentbox{margin-top:1em}#mnetconfig table{margin-left:auto;margin-right:auto}.environmenttable .cell{padding:.15em .5em}.environmenttable img.iconhelp{padding-right:.3em}.dir-rtl .environmenttable img.iconhelp{padding-left:.3em;padding-right:0}#trustedhosts .generaltable{margin-left:auto;margin-right:auto;width:500px}#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{width:640px;display:inline}.form-item .form-setting .form-htmlarea .htmlarea{width:640px;display:block}.form-item .form-setting .form-multicheckbox ul{list-style:none;padding:0;margin:7px 0 0 0}.form-item .form-setting .defaultsnext{margin-right:.5em;display:inline}.dir-rtl .form-item .form-setting .defaultsnext{margin-left:.5em;margin-right:0}.form-item .form-setting .locked-checkbox{margin-right:.2em;margin-left:.5em;display:inline}.dir-rtl .form-item .form-setting .locked-checkbox{margin-right:.5em;margin-left:.2em;display:inline}.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;height:102px;width:410px;margin-bottom:10px}.admin_colourpicker .loadingicon{vertical-align:middle;margin-left:auto}.admin_colourpicker .colourdialogue{float:left;border:1px solid #000}.admin_colourpicker .previewcolour{border:1px solid #000;margin-left:301px}.admin_colourpicker .currentcolour{border:1px solid #000;margin-left:301px;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}#page-admin-index .adminwarning.availableupdatesinfo .moodleupdateinfo.maturity200 .info.release{background-color:#d9edf7}#page-admin-index .adminwarning.availableupdatesinfo .moodleupdateinfo.maturity100 .info.release,#page-admin-index .adminwarning.availableupdatesinfo .moodleupdateinfo.maturity150 .info.release{background-color:#fcf8e3}#page-admin-index .adminwarning.availableupdatesinfo .moodleupdateinfo.maturity50 .info.release{background-color:#f2dede}#page-admin-plugins #plugins-overview-panel .info{display:inline-block;margin-right:1em}#page-admin-plugins .checkforupdates{margin:10px 0}#page-admin-plugins .checkforupdates .singlebutton{margin:5px 0;padding:0}#page-admin-plugins .checkforupdates .singlebutton div,#page-admin-plugins .checkforupdates .singlebutton input{margin:0 3px 0 0}#page-admin-plugins .updateavailableinstallall{margin:5px 0;padding:0}#page-admin-plugins .updateavailableinstallall div,#page-admin-plugins .updateavailableinstallall input{margin:0 3px 5px 0}#page-admin-plugins #plugins-control-panel .status-missing td{background-color:#f2dede}#page-admin-plugins #plugins-control-panel .pluginname .displayname img.icon{padding-top:0;padding-bottom:0}#page-admin-plugins #plugins-control-panel .pluginname .componentname{font-size:11.9px;color:#999;margin-left:22px}#page-admin-plugins #plugins-control-panel .version .versionnumber{font-size:11.9px;color:#999}#page-admin-plugins #plugins-control-panel .uninstall a{color:#b94a48}#page-admin-plugins #plugins-control-panel .notes .label{margin-right:3px}#page-admin-plugins #plugins-control-panel .notes .requiredby{font-size:11.9px;color:#999}#plugins-check-page .page-description{color:#999}#plugins-check-page .checkforupdates .singlebutton{margin:5px 0;padding:0}#plugins-check-page .checkforupdates .singlebutton div,#plugins-check-page .checkforupdates .singlebutton input{margin:0 3px 0 0}#plugins-check-page #plugins-check-info .actions>div{display:inline-block;margin-right:1em}#plugins-check-page #plugins-check-info .actions .singlebutton{margin:5px 0;padding:0}#plugins-check-page #plugins-check-info .actions .singlebutton div,#plugins-check-page #plugins-check-info .actions .singlebutton input{margin:0 3px 0 0}#plugins-check-page #plugins-check .requires-ok{color:#999}#plugins-check-page #plugins-check .status-missing td,#plugins-check-page #plugins-check .status-downgrade td{background-color:#f2dede}#plugins-check-page #plugins-check .displayname .pluginicon{margin-right:5px;width:16px}#plugins-check-page #plugins-check .displayname .plugindir{color:#999;font-size:11.9px}#plugins-check-page #plugins-check .requires ul{margin-left:13px}#plugins-check-page #plugins-check .status .actionbutton{margin:5px 0;padding:0}#plugins-check-page #plugins-check .status .actionbutton input{margin:0}#plugins-check-page .plugins-check-dependencies-actions>div{display:inline-block;margin-right:1em}#plugins-check-page .plugins-check-dependencies-actions .singlebutton{margin:5px 0;padding:0}#plugins-check-page .plugins-check-dependencies-actions .singlebutton div,#plugins-check-page .plugins-check-dependencies-actions .singlebutton input{margin:0 3px 0 0}#plugins-check-page #plugins-check-available-dependencies .displayname .component{font-size:11.9px;color:#999}#plugins-check-page #plugins-check-available-dependencies .info .actions>div{display:inline-block;margin-right:1em}#plugins-check-page #plugins-check-available-dependencies .info .actions .dependencyinstall{display:block;margin:5px 0;padding:0}#plugins-check-page #plugins-check-available-dependencies .info .actions .dependencyinstall input{margin:0}#plugins-check-page .pluginupdateinfo,#plugins-control-panel .pluginupdateinfo{background-color:#d9edf7;padding:5px;margin:10px 0;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}#plugins-check-page .pluginupdateinfo.maturity50,#plugins-control-panel .pluginupdateinfo.maturity50{background-color:#f2dede}#plugins-check-page .pluginupdateinfo.maturity100,#plugins-control-panel .pluginupdateinfo.maturity100,#plugins-check-page .pluginupdateinfo.maturity150,#plugins-control-panel .pluginupdateinfo.maturity150{background-color:#fcf8e3}#plugins-check-page .pluginupdateinfo .info,#plugins-control-panel .pluginupdateinfo .info{display:inline-block}#plugins-check-page .pluginupdateinfo .separator:after,#plugins-control-panel .pluginupdateinfo .separator:after{content:" | "}#plugins-check-page .pluginupdateinfo .singlebutton,#plugins-control-panel .pluginupdateinfo .singlebutton{margin:5px 0;padding:0}#plugins-check-page .pluginupdateinfo .singlebutton div,#plugins-control-panel .pluginupdateinfo .singlebutton div,#plugins-check-page .pluginupdateinfo .singlebutton input,#plugins-control-panel .pluginupdateinfo .singlebutton input{margin:0 3px 0 0}.plugins-management-confirm-buttons>div{display:inline-block;margin:1em 1em 1em 0}.plugins-management-confirm-buttons .continue{padding:0}.plugins-management-confirm-buttons .continue div,.plugins-management-confirm-buttons .continue input{margin:0}.uninstalldeleteconfirmexternal{background-color:#fcf8e3;padding:.5em 1em;margin:5px 0 10px 0}#page-admin-index .upgradepluginsinfo{text-align:center}#page-admin-index .adminwarning.availableupdatesinfo .moodleupdateinfo .separator:after{content:" | "}.dir-rtl #plugins-check .pluginupdateinfo{text-align:center;direction:ltr}.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{background-color:#f2dede;border:2px solid #eed3d7;padding:4px;margin-bottom:5px}#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}.maintenancewarning{padding:3px 1em;text-align:center;position:fixed;bottom:0;right:0;overflow:hidden;z-index:1}.maintenancewarning.error{color:#b94a48;background-color:#f2dede;border:2px solid #eed3d7;font-weight:bold}.maintenancewarning.warning{color:#8a6d3b;background-color:#fcf8e3;border:2px solid #fbeed5}#adminsettings .form-overridden{color:#3a87ad;background-color:#d9edf7}.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%;vertical-align:top;text-align:center;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{text-align:center;width:76%}.path-calendar .calendar-controls .next{text-align:right}.path-calendar .filters table{border-collapse:separate;border-spacing:2px;width:100%}.path-calendar .cal_courses_flt{float:left}.path-calendar .cal_courses_flt label{margin-right:.45em}.path-calendar .maincalendar{vertical-align:top;padding:0}.path-calendar .maincalendar .bottom{text-align:center;padding:5px 0 0 0}.path-calendar .maincalendar .heightcontainer{height:100%;position:relative}.path-calendar .maincalendar .calendarmonth{width:98%;margin:10px auto}.path-calendar .maincalendar .calendarmonth ul{margin:0}.path-calendar .maincalendar .calendarmonth ul li{list-style-type:none;margin-top:4px}.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-width:1px 1px 1px 12px;border-style:solid}.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-weight:bold;color:inherit;font-size:25px;line-height:1.2}.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{margin:0}.path-calendar .maincalendar .eventlist .event{width:92%;border-spacing:0;border-collapse:separate;position:relative;padding:20px 4%;margin-bottom:20px;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);list-style-type:none}.path-calendar .maincalendar .eventlist .event>img{padding-top:3px;float:left}.path-calendar .maincalendar .eventlist .event .name{font-size:17.5px;font-weight:200;line-height:24px;float:left;margin:0}.path-calendar .maincalendar .eventlist .event .name,.path-calendar .maincalendar .eventlist .event .course{margin-bottom:5px}.path-calendar .maincalendar .eventlist .event .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{background-color:#fff;padding:5px;clear:both}.path-calendar .maincalendar .eventlist .event .description .commands{position:absolute;right:0;top:0;margin:3px}.path-calendar .maincalendar .eventlist .event .commands{position:absolute;top:2px;right:2px}.path-calendar .maincalendar .eventlist .event .commands a{margin:0 3px}.dir-rtl.path-calendar .cal_courses_flt{float:right}.dir-rtl.path-calendar .cal_courses_flt label{margin-left:.45em;margin-right:0}.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-left-width:1px;border-right-width:12px}.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>img{float:right}.dir-rtl.path-calendar .maincalendar .eventlist .event .name{float:right}.dir-rtl.path-calendar .maincalendar .eventlist .event .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}.dir-rtl.path-calendar .maincalendar .eventlist .event .commands{left:2px;right:inherit}#page-calendar-export .indent{padding-left:20px}.block .minicalendar{max-width:280px;margin:0 auto;width:100%}.block .minicalendar th,.block .minicalendar td{padding:2px;font-size:.8em;text-align:center}.block .minicalendar td.weekend{color:#999}.block .minicalendar td a{width:100%;height:100%;display:block}.block .minicalendar td.duration_global{border-top:1px solid #d6f8cd;border-bottom:1px solid #d6f8cd}.block .minicalendar td.duration_global.duration_finish{background-color:#d6f8cd}.block .minicalendar td.duration_course{border-top:1px solid #ffd3bd;border-bottom:1px solid #ffd3bd}.block .minicalendar td.duration_course.duration_finish{background-color:#ffd3bd}.block .minicalendar td.duration_group{border-top:1px solid #fee7ae;border-bottom:1px solid #fee7ae}.block .minicalendar td.duration_group.duration_finish{background-color:#fee7ae}.block .minicalendar td.duration_user{border-top:1px solid #dce7ec;border-bottom:1px solid #dce7ec}.block .minicalendar td.duration_user.duration_finish{background-color:#dce7ec}.block .minicalendar caption{font-size:inherit;font-weight:inherit;line-height:inherit;text-align:center}.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{line-height:1.2;font-size:18px}.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{text-align:left;width:12%}.block .calendar-controls .current{text-align:center;width:76%}.block .calendar-controls .next{text-align:right;width:12%}.block .calendar_filters ul{list-style:none;margin:0}.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}.ical-link{font-size:10px;font-weight:bold;background-color:#f60;padding:0 5px;color:#fff;border-top:1px solid #f93;border-left:1px solid #f93;border-bottom:1px solid #013;border-right:1px solid #013}.ical-link:hover,.ical-link:active,.ical-link:focus,.ical-link:visited{color:#fff;text-decoration:none}@media (min-width:768px){#page-calender-view .container-fluid{min-width:1024px}}.section_add_menus{text-align:right;clear:both}.section-modchooser{clear:both}.dir-rtl .section_add_menus{text-align:left;clear:both}.section_add_menus .horizontal div,.section_add_menus .horizontal form{display:inline}.section_add_menus optgroup{font-weight:normal;font-style:italic}.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 .side.left{float:left}.section .side.right{float:right}.section .spinner{height:16px;width:16px}.section .activity .spinner{left:100%;position:absolute;vertical-align:text-bottom}.section .activity .editing_move{position:absolute;left:0;top:0}.section .activity .mod-indent-outer{padding-left:32px}.section .activity .actions{position:absolute;right:0;top:0}.section .activity .contentwithoutlink,.section .activity .activityinstance{min-width:40%;display:table-cell;padding-right:4px;min-height:2em}.section .activity .contentwithoutlink .dimmed img.activityicon,.section .activity .activityinstance .dimmed img.activityicon{opacity:.5;filter:alpha(opacity=50)}.section .label .contentwithoutlink,.section .label .activityinstance{padding-right:32px;display:block;height:inherit}.section .label .mod-indent-outer{padding-left:24px;display:block}.section .filler{width:16px;height:16px;padding:.3em;display:inline-block}.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{margin-bottom:initial;padding-top:initial;padding-bottom:initial;vertical-align:text-bottom}.dir-rtl .section .side.left{float:right}.dir-rtl .section .side.right{float:left}.dir-rtl .section .activity .spinner{left:auto;right:100%}.dir-rtl .section .activity .mod-indent-outer{padding-left:initial;padding-right:32px}.dir-rtl .section .activity .actions{left:0;right:auto}.dir-rtl .section .activity .contentwithoutlink,.dir-rtl .section .activity .activityinstance{padding-left:4px;padding-right:initial}.dir-rtl .section .activity .editing_move{left:auto;right:0}.dir-rtl .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-left:6px;margin-right:0}.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-left:200px;padding-right:0}.editing_show+.editing_assign,.editing_hide+.editing_assign{margin-left:20px}.section .activity .commands{white-space:nowrap;display:inline}.section .activity.modtype_label.label{font-weight:normal;padding:.2em}.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-left:0;margin-right:30px}.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{border:1px solid #ddd;margin-top:5px;list-style:none}.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{color:#999;font-size:11.9px;margin:3px;white-space:nowrap;display:inline-block}.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{font-weight:bold;font-size:108%;clear:both}.course-content .single-section .section-navigation .mdl-left{font-weight:normal;float:left;margin-right:1em}.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{font-weight:normal;float:right;margin-left:1em}.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{border-bottom:2px solid #ddd;margin-top:0}.course-content ul li.section.hidden .sectionname>span,.course-content ul li.section.hidden .content>div,.course-content ul li.section.hidden .activity .activityinstance{opacity:.5}.course-content ul li.section.hidden .sectionname>span,.course-content ul li.section.hidden .activity .activityinstance{margin-left:10px;margin-right:10px}.course-content ul.topics li.section .content,.course-content ul.weeks li.section .content{margin-right:20px;margin-left:20px;padding:0}.course-content{margin-top:0}.course-content ul.topics li.section{padding-bottom:20px}.course-content ul.topics li.section .summary{margin-left:25px}.course-content li.section ul{list-style:disc}.course-content li.section ul ul{list-style:circle}.course-content li.section ul ul ul{list-style:square}.course-content li.section li.activity ul{list-style:disc}.course-content li.section li.activity ul ul{list-style:circle}.course-content li.section li.activity ul ul ul{list-style:square}.path-course-view .completionprogress{margin-left:25px}.path-course-view .completionprogress{display:block;float:right;height:20px;position:relative}#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{width:16px;height:16px;position:absolute;right:22px;top:3px;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{text-align:center;clear:both}.section img.movetarget{height:16px;width:80px}input.titleeditor{width:330px;vertical-align:text-bottom}span.editinstructions{position:absolute;top:0;margin-top:-22px;margin-left:30px;line-height:16px;font-size:11.9px;padding:.1em .4em;background-color:#d9edf7;color:#3a87ad;text-decoration:none;z-index:9999;-webkit-box-shadow:2px 2px 5px 1px #ccc;-moz-box-shadow:2px 2px 5px 1px #ccc;box-shadow:2px 2px 5px 1px #ccc;border:1px solid #bce8f1}#dndupload-status{position:fixed;left:0;width:40%;margin:0 30%;padding:6px;border:1px solid #bce8f1;text-align:center;background:#d9edf7;color:#3a87ad;z-index:1;-webkit-box-shadow:2px 2px 5px 1px #ccc;-moz-box-shadow:2px 2px 5px 1px #ccc;box-shadow:2px 2px 5px 1px #ccc;-webkit-border-radius:8px;-moz-border-radius:8px;border-radius:8px}.dndupload-preview{color:#909090;border:1px dashed #909090;list-style:none;margin-top:.2em;padding:.3em}.dndupload-preview img.icon{vertical-align:text-bottom;padding:0}.dndupload-progress-outer{overflow:hidden;height:20px;margin-bottom:20px;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;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);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.dndupload-progress-inner{width:0;height:100%;color:#fff;float:left;font-size:12px;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{margin-bottom:15px;border:1px dotted #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;padding:5px}.coursebox>.info>.coursename a{display:block;background-image:url([[pix:moodle|i/course]]);background-repeat:no-repeat;padding-left:21px;background-position:left .2em}.dir-rtl .coursebox>.info>.coursename a{padding-left:0;padding-right:21px;background-position:right .2em}.coursebox>.info>.coursename,.coursebox .content .teachers,.coursebox .content .courseimage,.coursebox .content .coursefile{float:left;clear:left}.coursebox .content .teachers,.coursebox .content .courseimage,.coursebox .content .coursefile{width:40%}.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;line-height:1}.coursebox>.info>.coursename{margin:5px;padding:0}.coursebox .content .teachers li{list-style-type:none;padding:0;margin:0}.coursebox .enrolmenticons{padding:3px 0;float:right}.coursebox .moreinfo{padding:3px 0;float:right}.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{text-align:right;clear: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{margin:3px 5px;padding:0}.coursebox.remotehost>.info>.categoryname a{background-image:url([[pix:moodle|i/mnethost]])}.dir-rtl .coursebox>.info>.categoryname a{padding-left:0;padding-right:21px;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{text-align:left;clear:left}.coursebox.collapsed{margin-bottom:0}.coursebox.collapsed>.content{display:none}.courses .coursebox.collapsed{border:1px solid #ddd;padding:5px}.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{margin-bottom:5px;text-align:right;float:right}.course_category_tree .controls div{padding-right:2em;font-size:75%}.course_category_tree .category>.info>.categoryname{background-image:url([[pix:moodle|t/collapsed_empty]]);background-repeat:no-repeat;padding:2px 18px;margin:3px;background-position:center left}.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{background-image:url([[pix:moodle|t/expanded]]);cursor:pointer}.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;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);border-color:#e3e3e3;min-height:0;padding:0;margin:3px 0;margin-bottom:3px;clear:both}.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{background:none;border:none;margin:0}.course_category_tree .category>.content{padding-left:16px}.dir-rtl .course_category_tree .category>.content{padding-left:0;padding-right:16px}.course_category_tree .subcategories>.paging,.courses>.paging{margin:0;padding:5px;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{margin-bottom:15px;border:1px dotted #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;padding:5px}#page-course-index-category .categorypicker{text-align:center;margin:10px 0 20px}.section .summary .iconsmall,.section .activity .iconsmall{width:16px;height:16px}.section .editing_title .iconsmall{width:12px;height:12px;margin:8px 8px 0 0;padding:4px 8px 0 0;vertical-align:text-bottom}.section .moodle-actionmenu .iconsmall{max-width:none !important;width:16px;height:16px;padding:4px;vertical-align:text-bottom}.section .moodle-actionmenu[data-enhanced] .menu img{width:12px;height:12px}.dir-rtl .section .editing_title .iconsmall{margin:8px 0 0 8px;padding:4px 0 0 8px}#course-category-listings{background-color:transparent;margin-bottom:200px}#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{margin:0;padding:.4rem .6rem .3rem}#course-category-listings h4{margin:1rem 0 0;padding:.6rem 1rem .5rem}#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{text-align:center;padding:.4rem .3rem .3rem;line-height:2.2em}#course-category-listings .listing-actions>a,#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{color:inherit;display:inline-block}#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{list-style:none;margin:1rem 0}#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{margin:2px 6px 0 0;width:12px;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{margin-right:1em;display:inline-block;display:initial}#course-category-listings .item-actions>a img,#course-category-listings .item-actions .menubar img{margin:0 4px;height:12px;padding:0;vertical-align:inherit}#course-category-listings .item-actions.show .menu li{line-height:20px}#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{color:#a1a1a8;margin-right:2em}#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;text-align:center;margin:-1px .5em 0 0;padding-top:2px}#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{margin:0 .5em 0 0;padding: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{color:#a1a1a8;margin-right:2rem;min-width:3.5em;display:inline-block}#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{margin:0 .5em .5em;position:relative}#course-category-listings .detail-pair{border-bottom:1px solid #ddd;margin:0 1rem}#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{margin-right:1rem;display:block}#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{background-color:#fff;border:0;margin:.4rem .2rem .45rem;font-size:10.4px}#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{color:#999;margin:.4rem 1rem .45rem}#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 .course-count .smallicon{margin-left:0;margin-right:4px}.dir-rtl #course-category-listings #category-listing .bulk-action-checkbox{margin-left:-3px;margin-right:0}.dir-rtl #course-category-listings #course-listing{padding-right:24px}.dir-rtl #course-category-listings #course-listing .listitem .idnumber{color:#a1a1a8;padding-right:2em}.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{vertical-align:middle;margin:-2px 0 0 6px}.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{margin-left:1em;margin:10px 0;display:inline-block}.coursecat-management-header select{max-width:300px;cursor:pointer;padding:.4em .5em .45em 1em;vertical-align:baseline;white-space:nowrap}.coursecat-management-header .view-mode-selector .moodle-actionmenu{white-space:nowrap;display:inline-block}.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{border:0;color:#0070a8;vertical-align:middle;padding:0 0 0 4em}.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:#555555;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{height:100%;width:100%;display:table-cell;vertical-align:middle}.fp-content-hidden{visibility:hidden}.yui3-panel-focused{outline:none}#filesskin .yui3-panel-content{padding-bottom:20px;background:#F2F2F2;-webkit-border-radius:8px;-moz-border-radius:8px;border-radius:8px;border:1px solid #fff;display:inline-block;*display:inline;*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{-webkit-border-radius:10px 10px 0 0;-moz-border-radius:10px 10px 0 0;border-radius:10px 10px 0 0;border-bottom:1px solid #BBBBBB;padding:5px;text-align:center;font-size:12px;color:#333;letter-spacing:1px;text-shadow:1px 1px 1px #fff;filter:dropshadow(color=#FFFFFF, offx=1, offy=1);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;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffcccccc', GradientType=0)}.fp-panel-button{background:#fff;padding:3px 20px 2px 20px;text-align:center;margin:10px;-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px;display:inline-block;*display:inline;*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 h3{font-size:14px;margin:0;line-height:20px}.moodle-dialogue-base .filepicker .moodle-dialogue-wrap .moodle-dialogue-bd{padding:0}#filesskin .file-picker.fp-generallayout{width:859px;background:#FFFFFF;-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px;border:1px solid #CCCCCC;position:relative}.file-picker .fp-repo-area{width:180px;overflow:auto;display:inline-block;*display:inline;*zoom:1;float:left;height:525px;border-right:1px solid #BBBBBB}.dir-rtl .file-picker .fp-repo-area{border-left:1px solid #BBBBBB;border-right:none;float:right}.file-picker .fp-repo-items{float:none;width:auto;margin-left:181px}.moodle-dialogue-fullscreen .file-picker .fp-repo-items{margin-left:0;margin-right:0;float:left}.dir-rtl .file-picker .fp-repo-items{margin-left:0;margin-right:181px}.dir-rtl .moodle-dialogue-fullscreen .file-picker .fp-repo-items{margin-left:0;margin-right:0;float:right}.file-picker .fp-navbar{background:#F2F2F2;border-bottom:1px solid #BBBBBB;min-height:40px;overflow:hidden}.file-picker .fp-navbar .fp-viewbar{margin:4px}.file-picker .fp-content{background:#FFFFFF;clear:none;overflow:auto;height:452px}.filepicker.moodle-dialogue-fullscreen .file-picker .fp-content{width:100%}.file-picker .fp-content-loading{height:100%;width:100%;display:table;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{text-align:right;display:block;border-top:none}.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{list-style-type:none;padding:0;float:left;width:100%;margin:0}.dir-rtl .file-picker .fp-list{text-align:right;float:left}.file-picker .fp-list .fp-repo a{display:block;padding:.5em .7em}.file-picker .fp-list .fp-repo.active{background:#F2F2F2}.file-picker .fp-list .fp-repo-icon{padding:0 7px 0 5px;width:16px;height:16px}.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-left:4px;margin-right:0}.fp-toolbar img{vertical-align:-15%;margin-right:5px}.fp-toolbar .fp-tb-search{width:235px;height:27px}.fp-toolbar .fp-tb-search input{background:#FFFFFF url('[[pix:a/search]]') no-repeat 7px 7px;padding:2px 6px 1px 27px;width:200px;height:27px;border:1px solid #BBBBBB}.fp-viewbar{float:right;height:30px;border:1px solid #CCC;border-bottom:1px solid #B3B3B3;border-radius:4px;background:white}.fp-repo-items fp-viewbar{margin:4px}.dir-rtl .fp-toolbar img{vertical-align:-35%}.dir-rtl .fp-viewbar{float:left}.fp-viewbar a{width:30px;height:30px;border-right:1px solid #CCC;display:block;float:left}.fp-viewbar a.checked:hover,.fp-viewbar a:hover{background-image:radial-gradient(ellipse at center, #ffffff 60%, #dfdfdf 100%);background-color:#ebebeb}.fp-viewbar a.checked,.fp-viewbar a:active{background-image:radial-gradient(ellipse at center, #ffffff 40%, #dfdfdf 100%);background-color:#dfdfdf}.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{opacity:.45;background:none;cursor:default}.file-picker .fp-clear-left{clear:left}.dir-rtl .fp-vb-details a:hover{background:none;border:20px solid black}.dir-rtl .fp-vb-details.checked a:hover{background:none;border:40px solid black}.dir-rtl .fp-vb-tree a:hover{background:none;border:30px solid black}.dir-rtl .fp-vb-tree.checked a:hover{background:none;border:50px solid black}.file-picker .fp-pathbar{display:table-row}.fp-pathbar.empty{display:none}.fp-pathbar .fp-path-folder{background:url('[[pix:theme|fp/path_folder]]') no-repeat 0 0;width:27px;height:12px;margin-left:4px}.dir-rtl .fp-pathbar .fp-path-folder{background:url('[[pix:theme|fp/path_folder_rtl]]') no-repeat right top;width:auto;height:12px;margin-left:4px}.dir-rtl .fp-pathbar span{display:inline-block;*display:inline;*zoom:1;float:right;margin-left:32px}.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{float:left;text-align:center;position:relative;margin:10px 10px 35px}.fp-iconview .fp-thumbnail{min-width:110px;min-height:110px;line-height:110px;text-align:center;border:1px solid #FFFFFF;display:block}.fp-iconview .fp-thumbnail img{border:1px solid #ddd;padding:3px;vertical-align:middle;-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{height:33px;word-wrap:break-word;overflow:hidden;position:absolute}.fp-iconview .fp-filename-field:hover{overflow:visible;z-index:1000}.fp-iconview .fp-filename-field .fp-filename{background:#FFFFFF;padding-top:5px;padding-bottom:12px;min-width:112px}.dir-rtl .fp-iconview .fp-file{float:right}.file-picker .yui3-datatable table{border:0 solid #BBBBBB;width:100%}#filesskin .file-picker .yui3-datatable-header{background:#FFFFFF;border-bottom:1px solid #CCCCCC;border-left:0 solid #FFFFFF;color:#555555}#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:#FFFFFF;border-left:0 solid #FFFFFF}.dir-rtl .file-picker .yui3-datatable-header{text-align:right}.file-picker .ygtvtn,.filemanager .ygtvtn{background:url('[[pix:moodle|y/tn]]') 0 0 no-repeat;width:17px;height:22px}.dir-rtl .filemanager .ygtvtn,.dir-rtl .file-picker .ygtvtn{background:url('[[pix:moodle|y/tn_rtl]]') 0 0 no-repeat;width:17px;height:22px}.file-picker .ygtvtm,.filemanager .ygtvtm{background:url('[[pix:moodle|y/tm]]') 0 10px no-repeat;width:13px;height:12px;cursor:pointer}.file-picker .ygtvtmh,.filemanager .ygtvtmh{background:url('[[pix:moodle|y/tm]]') 0 10px no-repeat;width:13px;height:12px;cursor:pointer}.file-picker .ygtvtp,.filemanager .ygtvtp{background:url('[[pix:moodle|y/tp]]') 0 10px no-repeat;width:13px;height:12px;cursor:pointer}.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{background:url('[[pix:moodle|y/tp]]') 0 10px no-repeat;width:13px;height:22px;cursor:pointer}.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{background:url('[[pix:moodle|y/ln]]') 0 0 no-repeat;width:17px;height:22px}.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{background:url('[[pix:moodle|y/lm]]') 0 10px no-repeat;width:13px;height:12px;cursor:pointer}.file-picker .ygtvlmh,.filemanager .ygtvlmh{background:url('[[pix:moodle|y/lm]]') 0 10px no-repeat;width:13px;height:12px;cursor:pointer}.file-picker .ygtvlp,.filemanager .ygtvlp{background:url('[[pix:moodle|y/lp]]') 0 10px no-repeat;width:13px;height:12px;cursor:pointer}.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{background:url('[[pix:moodle|y/lp]]') 0 10px no-repeat;width:13px;height:12px;cursor:pointer}.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{background:transparent url('[[pix:moodle|y/loading]]') 0 0 no-repeat;width:16px;height:22px}.file-picker .ygtvdepthcell,.filemanager .ygtvdepthcell{background:url('[[pix:moodle|y/vline]]') 0 0 no-repeat;width:17px;height:32px}.file-picker .ygtvblankdepthcell,.filemanager .ygtvblankdepthcell{width:17px;height:22px}a.ygtvspacer:hover{color:transparent;text-decoration:none}.ygtvlabel,.ygtvlabel:link,.ygtvlabel:visited,.ygtvlabel:hover{background-color:transparent;cursor:pointer;margin-left:2px;text-decoration:none}.file-picker .ygtvfocus,.filemanager .ygtvfocus{background-color:#EEEEEE}.fp-filename-icon{margin-top:10px;display:block;position:relative}.fp-icon{float:left;margin-top:-7px;width:24px;height:24px;margin-right:10px;text-align:center;line-height:24px}.dir-rtl .fp-icon{float:right;margin-left:10px;margin-right:0}.fp-icon img{max-height:24px;max-width:24px;vertical-align:middle}.fp-filename{padding-right:10px}.dir-rtl .fp-filename{padding-left:10px;padding-right:0}.file-picker .fp-login-form{height:100%;width:100%;display:table}.file-picker .fp-login-form table{margin:0 auto}.file-picker .fp-login-form p{text-align:center;margin-top:3em}.file-picker .fp-login-form .fp-login-input label{text-align:right;display:block}.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{height:100%;width:100%;display:table}.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{padding:40px 20px 10px 20px;min-width:200px;max-width:500px;max-height:300px;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{height:100%;width:100%;display:table;text-align:center}.file-picker .fp-content-error .fp-error{height:100%;width:100%;display:table-cell;vertical-align:middle;padding:40px 20px 10px 20px;font-size:12px}.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;text-align:center;height:100px;padding-top:50px}.fp-select form{padding:20px 20px 0}.fp-select .fp-select-loading{text-align:center;margin-top:20px}.fp-select .fp-hr{clear:both;height:1px;background-color:#FFFFFF;border-bottom:1px solid #BBBBBB;width:auto;margin:10px 0}.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;clear:both;padding:1px 20px 0}.fp-select .fp-thumbnail{float:left;min-width:110px;min-height:110px;line-height:110px;text-align:center;margin:10px 20px 0 0;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{border:1px solid #DDDDDD;padding:3px;vertical-align:middle;margin:10px}.fp-select .fp-fileinfo{display:inline-block;*display:inline;*zoom:1;margin-top:10px}.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}.fp-select .fp-size.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 #BBBBBB;border-bottom:none}.filemanager-toolbar{padding:4px;overflow:hidden}.fp-pathbar{border-top:1px solid #BBBBBB;padding:5px 8px 1px;min-height:20px}.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{border:1px solid #CCC;border-bottom:1px solid #B3B3B3;border-radius:4px;background:white;width:30px;height:30px}.fp-toolbar a:hover{background-image:radial-gradient(ellipse at center, #ffffff 60%, #dfdfdf 100%);background-color:#ebebeb}.fp-toolbar a:active{background-image:radial-gradient(ellipse at center, #ffffff 40%, #dfdfdf 100%);background-color:#dfdfdf}.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{background:#FFFFFF;clear:both;overflow:auto;border:1px solid #BBBBBB;min-height:140px;position:relative}.filemanager .fp-content{overflow:auto;max-height:472px;min-height:157px}.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;height:100%;width:100%;top:0;left:0}.fp-iconview .fp-reficons2{position:absolute;height:100%;width:100%;top:0;left:0}.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{border:0 solid #BBBBBB;width:100%}.filemanager .yui3-datatable-header{background:#FFFFFF !important;border-bottom:1px solid #CCCCCC !important;border-left:0 solid #FFFFFF !important;color:#555555 !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:#FFFFFF !important;border-left:0 solid #FFFFFF}.filemanager .fp-filename-icon.fp-hasreferences .fp-reficons1{background:url('[[pix:theme|fp/link_sm]]') no-repeat 0 0;height:100%;width:100%;position:absolute;top:8px;left:17px;z-index:1000}.filemanager .fp-filename-icon.fp-isreference .fp-reficons2{background:url('[[pix:theme|fp/alias_sm]]') no-repeat 0 0;height:100%;width:100%;position:absolute;top:9px;left:-6px;z-index:1001}.filemanager .fp-contextmenu{display:none}.filemanager .fp-iconview .fp-folder.fp-hascontextmenu .fp-contextmenu{display:block;position:absolute;right:7px;bottom:5px}.filemanager .fp-treeview .fp-folder.fp-hascontextmenu .fp-contextmenu,.filemanager .fp-tableview .fp-folder.fp-hascontextmenu .fp-contextmenu{display:inline;position:absolute;left:14px;margin-right:-20px;top:6px}.dir-rtl .filemanager .fp-iconview .fp-folder.fp-hascontextmenu .fp-contextmenu{left:7px;right:inherit}.dir-rtl .filemanager .fp-treeview .fp-folder.fp-hascontextmenu .fp-contextmenu,.dir-rtl .filemanager .fp-tableview .fp-folder.fp-hascontextmenu .fp-contextmenu{left:inherit;right:16px;margin-right:0}.filepicker-filelist .filepicker-container,.filemanager.fm-noitems .fm-empty-container{display:block;position:absolute;top:10px;bottom:10px;left:10px;right:10px;border:2px dashed #BBBBBB;padding-top:85px;text-align:center}.filepicker-filelist .dndupload-target,.filemanager-container .dndupload-target{background:#FFFFFF;position:absolute;top:10px;bottom:10px;left:10px;right:10px;border:2px dashed #fb7979;padding-top:85px;text-align:center;-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{background:#FFFFFF;position:absolute;top:10px;bottom:10px;left:10px;right:10px;border:2px dashed #6c8cd3;padding-top:85px;text-align:center}.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{background:url([[pix:theme|fp/dnd_arrow]]) center no-repeat;width:100%;height:80px;position:absolute;top:5px}.fitem.disabled .filepicker-container,.fitem.disabled .fm-empty-container{display:none}.dndupload-progressbars{padding:10px;display:none}.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{background:#F9F9F9;border:1px solid #BBBBBB;padding:8px 7px;margin:0;max-width:265px;max-height:75px;overflow:auto}.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{text-align:left;margin:20px}.dir-rtl .filemanager .fp-mkdir-dlg p{text-align:right}.filemanager.fp-dlg{text-align:center}.filemanager.fp-dlg .fp-dlg-text{padding:0 10px;min-width:200px;max-width:340px;max-height:300px;overflow:auto;line-height:22px;margin:40px 20px 20px;font-size:12px}.file-picker div.bd{text-align:left}.dir-rtl .filemanager .fp-restrictions{text-align:left}.dir-rtl .file-picker div.bd,.dir-rtl .file-picker .fp-pathbar,.dir-rtl .file-picker .fp-list,.dir-rtl #filemenu .yuimenuitemlabel,.dir-rtl .filemanager-container .yui3-skin-sam .yui3-datatable-header{text-align:right}.dir-rtl .filepicker .yui-layout-unit-left{left:500px}.dir-rtl .filepicker .yui-layout-unit-center{left:0}.dir-rtl .file-picker .fp-toolbar .fp-tb-search input{background-position:208px 7px;padding:2px 30px 1px 3px}.dir-rtl .file-picker .fp-toolbar div{float:right;margin-left:4px}.fp-formset{max-width:500px;padding:10px}.fp-formset input[type="file"]{line-height:inherit}.fp-forminset{max-width:400px;padding:0 10px}.fp-forminset .control-group.control-radio{margin-bottom:0}.fp-forminset .control-group label.control-label{width:105px}.fp-forminset .control-group label.control-radio{float:right;text-align:left;width:215px}.fp-forminset .control-group .controls{margin-left:125px}.fp-forminset .control-group .controls select{width:100%}.fp-forminset .control-group .controls.control-radio input{margin-top:3px}.fp-forminset .fp-select-buttons{float:none}.fp-forminset input[type="text"]{width:228px}.fp-fileinfo .fp-value{display:inline-block;padding-left:5px}.dir-rtl .fp-forminset{max-width:400px}.dir-rtl .fp-forminset .control-group label.control-label{float:right;text-align:left}.dir-rtl .fp-forminset .control-group label.control-radio{float:left;text-align:right;width:215px}.dir-rtl .fp-forminset .control-group .controls{margin-left:0;margin-right:125px}.dir-rtl .fp-forminset .fp-select-buttons{float:left}.dir-rtl .fp-forminset input[type="text"]{width:228px}.dir-rtl .fp-fileinfo .fp-value{display:inline-block;padding-right:5px}.dir-rtl .fp-select .fp-thumbnail{margin:10px 0 0 0}.dir-rtl .filepicker .fp-formset label{float:right;text-align:left}.dir-rtl .filepicker .fp-formset .controls{margin-left:0;text-align:right}.message-discussion-noframes h1{font-size:1em}.message-discussion-noframes #userinfo .commands,.message .noframesjslink,.message .link{font-size:11.9px}.message .heading{font-size:1em;font-weight:bold;clear:both;word-wrap:break-word}.message .author{font-weight:bold}.message .time{font-style:italic}#page-message-user .commands span{font-size:.7em}#page-message-user .name{font-weight:bold;font-size:1.1em}.message .time{color:#999}#page-message-messages{padding:10px}#page-message-send .notifysuccess{padding:1px}#page-message-send td.fixeditor{text-align:center}.message{overflow:hidden}.message .note{padding:10px}table.message .searchresults td{padding:5px}.message .contactselector{max-width:380px;margin:0 auto;width:auto}@media screen and (min-width:1000px){.message .contactselector{float:left;padding:0 8px 0 0}}.message .contactselector .singleselect{width:240px}.message .contactselector .paging{z-index:1;position:relative}.message .contactselector #message_participants{max-width:240px}.message .contactselector .message-contacts{list-style-type:none;margin:0}.message .contactselector .message-contacts li{clear:both;position:relative;min-height:23px;padding:2px}.message .contactselector .message-contacts li:nth-child(odd){background:rgba(120,120,255,0.1)}.message .contactselector .message-contacts li>div{display:block;height:100%}.message .contactselector .message-contacts li>div>*{vertical-align:middle;display:inline-block}.message .contactselector .message-contacts li>div.pix{position:relative;float:left}.message .contactselector .message-contacts li>div.link{white-space:nowrap;width:60px;position:relative;float:right;text-align:right}.message .contactselector .message-contacts li>div.contact{position:relative;word-break:break-all}.message .contactselector .message-contacts li>div.contact a{line-height:22px}.dir-ltr .message .message-contacts li{text-align:left}.dir-ltr .message .message-contacts li>div.pix{float:left}.dir-ltr .message .message-contacts li>div.link{float:right;text-align:right}.dir-ltr .message .message-contacts li>div.contact{margin:0 60px 0 24px}.dir-ltr .message .message-contacts li>div.contact.nolinks{margin:0 0 0 24px}.dir-rtl .message .message-contacts li{text-align:right}.dir-rtl .message .message-contacts li>div.pix{float:right}.dir-rtl .message .message-contacts li>div.link{float:left;text-align:left}.dir-rtl .message .message-contacts li>div.contact{margin:0 24px 0 60px}.dir-rtl .message .message-contacts li>div.contact.nolinks{margin:0 24px 0 0}@media screen and (min-width:1000px){.dir-rtl .message .contactselector{float:right}}.message .messagearea{float:none;overflow:hidden;min-height:200px;min-width:300px}@media screen and (min-width:1000px){.message .messagearea{border-left:1px solid #ddd;padding:0 8px}}@media screen and (max-width:1000px){.message .messagearea{width:100%}}#message_user_pictures{text-align:center}.dir-rtl #message_user_pictures{direction:rtl}.message .messagearea .messagehistorytype{clear:both;padding-bottom:20px}.message .messagearea .messagehistory .user{vertical-align:top;width:45%;min-width:100px;display:inline-block}.message .messagearea .messagehistory .user>div{text-align:center}.message .messagearea .messagehistory .between{display:inline-block;width:16px;margin:0 1%;padding-top:40px}@media screen and (max-width:320px){.message .messagearea{min-width:0}.message .messagearea .messagehistory .user{max-width:70px;min-width:0}.message .messagearea .messagehistory .user .userpicture{width:50px;height:auto}}@media screen and (min-width:800px){.message .messagearea .messagehistory .between{margin:0 3%}.message .messagearea .messagehistory .user{width:32%}}@media screen and (min-width:1200px){.message .messagearea .messagehistory .user{width:25%}}.message .messagearea .messagehistory .heading{width:100%;clear:both}.message .messagearea .messagehistory .left{margin-bottom:10px;width:50%;float:left;position:relative;clear:both}.dir-rtl .message .messagearea .messagehistory .left{float:right}.message .messagearea .messagehistory .right{margin-bottom:10px;width:50%;float:right;position:relative;clear:both}.dir-rtl .message .messagearea .messagehistory .right{float:left}.message .messagearea .messagehistory .notification{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);border-color:#e3e3e3;padding:9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;margin-bottom:0;margin-top:5px}.message .messagearea .messagehistory .notification blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}.dir-ltr .message .messagearea .messagehistory .message{margin-right:20px}.dir-ltr .message .messagearea .messagehistory .right .message{margin-left:20px}.dir-rtl .message .messagearea .messagehistory .message{margin-left:20px}.dir-rtl .message .messagearea .messagehistory .right .message{margin-right:20px}.message .messagearea .messagehistory .messageactive{background-color:#f5f5f5}.message .messagearea .messagehistory .messagecontent .deleteicon{width:20px;position:absolute;top:-2px}.dir-ltr .message .messagearea .messagehistory .messagecontent .deleteicon{right:0}.dir-rtl .message .messagearea .messagehistory .messagecontent .deleteicon{left:0}.message .messagearea .messagesend{padding-top:20px;clear:both}.message .messagearea .messagesend .messagesendbox{width:100%;box-sizing:border-box}.message .messagearea .messagesend fieldset{padding:0;margin:0}.message .messagearea .messagerecent{text-align:left;width:100%}.message .messagearea .messagerecent .singlemessage{border-bottom:1px solid #ddd;padding:10px}.message .messagearea .messagerecent .singlemessage .otheruser span{padding:5px}.message .messagearea .messagerecent .singlemessage .messagedate{float:right}.message .hiddenelement{display:none}.message .visible{display:inline}.message #usergroupselector.fieldset,.message #viewing{width:100%}.messagesearchresults{margin-bottom:40px}.messagesearchresults td{padding:0 10px 0 20px}.messagesearchresults td span{white-space:nowrap}.messagesearchresults td img.userpicture{padding-right:.45em;vertical-align:text-bottom}.dir-rtl .messagesearchresults td img.userpicture{padding-left:.45em;padding-right:0}.messagesearchresults td span img{padding:0 0 0 .45em;vertical-align:text-bottom}.dir-rtl .messagesearchresults td span img{padding:0 .45em 0 0}#newmessageoverlay{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);border-color:#e3e3e3;margin:0 1em;position:fixed;bottom:0;right:0}#newmessageoverlay blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}#newmessageoverlay #usermessage{padding:10px}#page-user-action_redir #edit-messagebody{width:auto}.core_message-messenger-sendmessage-hidden{display:none}.core_message-messenger-sendmessage .message-actions{position:relative}.core_message-messenger-sendmessage .message-area{height:240px;max-height:100%;position:relative;margin-bottom:10px}.core_message-messenger-sendmessage .message-input{width:100%;height:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.core_message-messenger-sendmessage .message-send{margin:0;float:right}.core_message-messenger-sendmessage .message-notice-area{display:table;position:absolute;top:0;bottom:0;left:0;right:0;width:100%;height:100%}.core_message-messenger-sendmessage .message-notice{display:table-cell;vertical-align:middle;text-align:center}.core_message-messenger-sendmessage .message-notice>div{background:#eee;padding:5px;font-size:12px}.core_message-messenger-sendmessage .message-footer{margin-top:3px;line-height:20px}.core_message-messenger-sendmessage .message-history{position:absolute;bottom:0}.dir-rtl .core_message-messenger-sendmessage .message-send{float:left}.questionbank h2{margin-top:0}.questioncategories h3{margin-top:0}#chooseqtypebox{margin-top:1em}#chooseqtype h3{margin:0 0 .3em}#chooseqtype .instruction{display:none}#chooseqtype .fakeqtypes{border-top:1px solid silver}#chooseqtype .qtypeoption{margin-bottom:.5em}#chooseqtype label{display:block}#chooseqtype .qtypename img{padding:0 .3em}#chooseqtype .qtypename{display:inline-table;width:16em}#chooseqtype .qtypesummary{display:block;margin:0 2em}#chooseqtype .submitbuttons{margin:.7em 0;text-align:center}#qtypechoicecontainer{display:none}#qtypechoicecontainer_c.yui-panel-container.shadow .underlay{background:none}#qtypechoicecontainer.yui-panel .hd{color:#333;letter-spacing:1px;text-shadow:1px 1px 1px #fff;-webkit-border-top-right-radius:10px;-moz-border-radius-topright:10px;border-top-right-radius:10px;-webkit-border-top-left-radius:10px;-moz-border-radius-topleft:10px;border-top-left-radius:10px;border:1px solid #ccc;border-bottom:1px solid #bbb;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;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffcccccc', GradientType=0)}#qtypechoicecontainer{font-size:12px;color:#333;background:#F2F2F2;-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px;border:1px solid #ccc;border-top:0 none;-webkit-box-shadow:5px 5px 20px 0 #666;-moz-box-shadow:5px 5px 20px 0 #666;box-shadow:5px 5px 20px 0 #666}#qtypechoicecontainer #chooseqtype{width:40em}#chooseqtypehead h3{margin:0;font-weight:normal}#chooseqtype .qtypes{position:relative;border-bottom:1px solid #bbb;padding:.24em 0}#chooseqtype .alloptions{overflow-x:hidden;overflow-y:auto;max-height:400px;max-height:calc(85vh);max-height:60vh;width:60%}#chooseqtype .qtypeoption{margin-bottom:0;padding:.3em .3em .3em 1.6em}#chooseqtype .qtypeoption img{vertical-align:text-bottom;padding-left:1em;padding-right:.5em}#chooseqtype .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}#chooseqtype .instruction,#chooseqtype .qtypesummary{display:none;position:absolute;top:0;right:0;bottom:0;left:60%;margin:0;overflow-x:hidden;padding:1.5em 1.6em;background-color:#fff;overflow-y:auto}#chooseqtype .instruction,#chooseqtype .selected .qtypesummary{display:block}#categoryquestions{margin:0}#categoryquestions td,#categoryquestions th{padding:0 .2em}#categoryquestions th{text-align:left;font-weight:normal}#categoryquestions .checkbox{padding-left:5px}#categoryquestions .checkbox input[type="checkbox"]{margin-left:0;float:none}#categoryquestions img.iconsmall{padding:0}#categoryquestions .iconcol{padding:3px}#categoryquestions label{margin:0}#page-mod-quiz-edit div.questionbankwindow div.header{margin:0}#page-mod-quiz-edit div.questionbankwindow.block{padding:0}.dir-rtl #categoryquestions th{text-align:right}.questionbank .singleselect{margin:0}#combinedfeedbackhdr div.fhtmleditor{padding:0}#combinedfeedbackhdr div.fcheckbox{margin-bottom:1em}#multitriesheader div.fitem_feditor{margin-top:1em}#multitriesheader div.fitem_fgroup{margin-bottom:1em}#multitriesheader div.fitem_fgroup fieldset.felement label{margin-left:.3em;margin-right:.3em}body.path-question-type .fitem_fgroup .accesshide{font:inherit;left:0;position:static;padding-right:.3em}.que{clear:left;text-align:left;margin:0 auto 1.8em auto}.dir-rtl .que{text-align:right}.que .info{float:left;width:7em;padding:.5em;margin-bottom:1.8em;background-color:#eee;border:1px solid #dcdcdc;-webkit-border-radius:2px;-moz-border-radius:2px;border-radius:2px}.que h3.no{margin:0;font-size:.8em;line-height:1}.que span.qno{font-size:1.5em;font-weight:bold}.que .info>div{font-size:.8em;margin-top:.7em}.que .info .questionflag.editable{cursor:pointer}.que .info .editquestion img,.que .info .questionflag img,.que .info .questionflag input{vertical-align:bottom}.que .content{margin:0 0 0 8.5em}.que .formulation,.que .outcome,.que .comment{padding:8px 35px 8px 14px;margin-bottom:20px;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;color:#8a6d3b}.que .formulation{background-color:#d9edf7;border-color:#bce8f1;color:#3a87ad;color:#333}.formulation input[type="text"],.formulation select{width:auto;vertical-align:baseline}.path-mod-quiz input[size]{width:auto}.que .comment{background-color:#dff0d8;border-color:#d6e9c6;color:#468847}.que .history{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);border-color:#e3e3e3}.que .history blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}.que .ablock{margin:.7em 0 .3em 0}.que .im-controls{margin-top:.5em;text-align:left}.dir-rtl .que .im-controls{text-align:right}.que .specificfeedback,.que .generalfeedback,.que .rightanswer,.que .im-feedback,.que .feedback,.que p{margin:0 0 .5em}.que .qtext{margin-bottom:1.5em}.que .correctness{display:inline-block;padding:2px 4px;font-size:11.844px;font-weight:bold;line-height:14px;color:#fff;vertical-align:baseline;white-space:nowrap;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#999;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.que .correctness:empty{display:none}.que .correctness-important{background-color:#b94a48}.que .correctness-important[href]{background-color:#953b39}.que .correctness-warning{background-color:#f89406}.que .correctness-warning[href]{background-color:#c67605}.que .correctness-success{background-color:#468847}.que .correctness-success[href]{background-color:#356635}.que .correctness-info{background-color:#3a87ad}.que .correctness-info[href]{background-color:#2d6987}.que .correctness-inverse{background-color:#333}.que .correctness-inverse[href]{background-color:#1a1a1a}.que .correctness.correct{background-color:#468847}.que .correctness.partiallycorrect{background-color:#f89406}.que .correctness.notanswered,.que .correctness.incorrect{background-color:#b94a48}.que .validationerror{color:#b94a48}.formulation .correct{background-color:#dff0d8}.formulation .partiallycorrect{background-color:#fcf8e3}.formulation .incorrect{background-color:#f2dede}.formulation select.correct,.formulation input.correct{color:#468847;background-color:#dff0d8;border-color:#468847;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.formulation select.correct:focus,.formulation input.correct:focus{border-color:#356635;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b}.formulation select.partiallycorrect,.formulation input.partiallycorrect{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.formulation select.partiallycorrect:focus,.formulation input.partiallycorrect:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #c0a16b;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #c0a16b}.formulation select.incorrect,.formulation input.incorrect{color:#b94a48;background-color:#f2dede;border-color:#b94a48;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.formulation select.incorrect:focus,.formulation input.incorrect:focus{border-color:#953b39;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392}.que .grading,.que .comment,.que .commentlink,.que .history{margin-top:.5em}.que .history h3{margin:0 0 .2em;font-size:1em}.que .history table{width:100%;margin:0}.que .history .current{font-weight:bold}.que .questioncorrectnessicon{vertical-align:text-bottom}.que input.questionflagimage{padding-right:3px}.dir-rtl .que input.questionflagimage{padding-left:3px;padding-right:0}.importerror{margin-top:10px;border-bottom:1px solid #555}.mform .que.comment .fitemtitle{width:20%}#page-question-preview #techinfo{margin:1em 0}.dir-rtl #chooseqtype .instruction,.dir-rtl #chooseqtype .qtypesummary{right:60%;left:0;border-left:0;border-right:1px solid grey}#page-mod-quiz-edit .box.generalbox.questionbank{padding:.5em}#page-mod-quiz-edit .questionbank .categorypagingbarcontainer,#page-mod-quiz-edit .questionbank .categoryquestionscontainer,#page-mod-quiz-edit .questionbank .choosecategory{padding:0}#page-mod-quiz-edit .questionbank .choosecategory select{width:100%}#page-mod-quiz-edit div.questionbank .categoryquestionscontainer{background:transparent}#page-mod-quiz-edit #categoryquestions>thead{background:#FFF}#page-mod-quiz-edit #categoryquestions>tbody>tr:nth-of-type(even){background:#e4e4e4}#page-mod-quiz-edit .questionbankwindow div.header{color:#444;text-shadow:none;padding:3px;-webkit-border-top-right-radius:4px;-moz-border-radius-topright:4px;border-top-right-radius:4px;-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px;margin:0 -10px 0 -10px;padding:2px 10px 2px 10px;background:transparent}#page-mod-quiz-edit .questionbankwindow div.header a:link,#page-mod-quiz-edit .questionbankwindow div.header a:visited{color:#0070a8}#page-mod-quiz-edit .questionbankwindow div.header a:hover{color:#003d5c}#page-mod-quiz-edit .createnewquestion{padding:.3em 0}#page-mod-quiz-edit .createnewquestion div,#page-mod-quiz-edit .createnewquestion input{margin:0}#page-mod-quiz-edit .questionbankwindow div.header .title{color:#333}#page-mod-quiz-edit div.container div.generalbox{background-color:transparent;padding:1.5em}#page-mod-quiz-edit .categoryinfo{background-color:transparent;border-bottom:none}#page-mod-quiz-edit .createnewquestion .singlebutton input{margin-bottom:0}#page-mod-quiz-edit div.questionbank .categorysortopotionscontainer,#page-mod-quiz-edit div.questionbank .categoryselectallcontainer{padding:0 0 1.5em 0}#page-mod-quiz-edit div.questionbank .categorypagingbarcontainer{background-color:transparent;margin:0;border-top:0;border-bottom:0}#page-mod-quiz-edit div.questionbank .categorypagingbarcontainer .paging{padding:0 .3em}#page-mod-quiz-edit div.question div.content div.questioncontrols{background-color:#fff}#page-mod-quiz-edit div.question div.content div.points{margin-top:-0.5em;padding-bottom:0;border:none;background-color:#fff;position:static;width:12.1em;float:right;margin-right:60px}#page-mod-quiz-edit.dir-rtl div.question div.content div.points{float:left;margin-left:60px;margin-right:0}#page-mod-quiz-edit div.question div.content div.points br{display:none}#page-mod-quiz-edit div.question div.content div.points label{display:inline-block}#page-mod-quiz-edit div.quizpage .pagecontent .pagestatus{background-color:#fff}#page-mod-quiz-edit .quizpagedelete,#page-mod-quiz-edit .quizpagedelete img{background-color:transparent}#page-mod-quiz-edit div.quizpage .pagecontent{border:1px solid #ddd;-webkit-border-radius:2px;-moz-border-radius:2px;border-radius:2px;overflow:hidden}#page-mod-quiz-edit div.questionbank .categoryinfo{padding:.3em 0}#page-mod-quiz-edit div.questionbank .modulespecificbuttonscontainer{padding:0}#page-mod-quiz-edit div.questionbank .modulespecificbuttonscontainer strong{display:block}#page-mod-quiz-edit div.questionbank .modulespecificbuttonscontainer hr,#page-mod-quiz-edit div.questionbank .modulespecificbuttonscontainer br{display:none}#page-mod-quiz-edit div.questionbank .modulespecificbuttonscontainer strong{margin-left:-0.3em}#page-mod-quiz-edit div.questionbank .modulespecificbuttonscontainer strong label{margin-left:.3em}#page-mod-quiz-edit div.questionbank .modulespecificbuttonscontainer input{margin-left:0}#page-mod-quiz-edit div.questionbank .modulespecificbuttonscontainer input+input{margin-left:5px}.questionbankwindow .module{width:auto}#page-mod-quiz-edit div.editq div.question div.content{background-color:#fff;border:1px solid #ddd;-webkit-border-radius:2px;-moz-border-radius:2px;border-radius:2px;overflow:hidden}.path-mod-quiz .statedetails{display:block;font-size:.9em}a#hidebankcmd{color:#0070a8}.que.shortanswer .answer{padding:0}.que label{display:inline}body.path-question-type .mform fieldset.hidden{padding:0;margin:.7em 0 0}.userprofile .fullprofilelink{text-align:center;margin:10px}.userprofile .page-context-header{margin-bottom:10px}.userprofile .description{margin-top:10px;margin-bottom:30px}.userprofile .profile_tree{-webkit-column-count:2;-moz-column-count:2;column-count:2;-webkit-column-gap:20px;-moz-column-gap:20px;column-gap:20px}.userprofile .profile_tree section{display:inline-block;width:100%;border:1px solid #ddd;border-radius:4px;padding:0 15px;margin-bottom:20px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.userprofile .profile_tree section h3{font-size:18px;line-height:20px}.userprofile dl.list{*zoom:1}.userprofile dl.list:before,.userprofile dl.list:after{display:table;content:"";line-height:0}.userprofile dl.list:after{clear:both}.userprofile dl.list dt{float:left;width:180px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.userprofile dl.list dd{margin-left:200px}.user-box{margin:8px;width:115px;height:160px;text-align:center;float:left;clear:none}#page-user-profile .node_category ul,.path-user .node_category ul{margin-left:0;margin-right:0;list-style:none}#page-user-profile .node_category li,.path-user .node_category li{margin-top:5px}#page-user-profile .node_category .editprofile,.path-user .node_category .editprofile,#page-user-profile .node_category .viewmore,.path-user .node_category .viewmore{text-align:right}.dir-rtl .user-box{float:right}.dir-rtl .userprofile .node_category .editprofile,.dir-rtl .userprofile .node_category .viewmore{text-align:left}.dir-rtl .userprofile dl dd{margin-left:0;margin-right:10px}@media (max-width:480px){.userprofile .profile_tree{-webkit-column-count:1;-moz-column-count:1;column-count:1;-webkit-column-gap:20px;-moz-column-gap:20px;column-gap:20px}}.userlist .action-icon img{vertical-align:middle}.userlist #showall{margin:10px 0}.userlist .buttons{text-align:center}.userlist .buttons label{padding:0 3px}.userlist table#participants{text-align:center}.userlist table#participants td,.userlist table#participants th{vertical-align:middle;text-align:left;padding:4px}.userlist table.controls{width:100%}.userlist table.controls tr{vertical-align:top}.userlist table.controls .right{text-align:right}.userlist table.controls .groupselector{margin-bottom:0;margin-top:0}.userlist table.controls .groupselector label{display:block}.userinfobox{width:100%;border:1px solid;border-collapse:separate;padding:10px}.userinfobox .left,.userinfobox .side{width:100px;vertical-align:top}.userinfobox .userpicture{width:100px;height:100px}.userinfobox .content{vertical-align:top}.userinfobox .links{width:100px;padding:5px;vertical-align:bottom}.userinfobox .links a{display:block}.userinfobox .list td{padding:3px}.userinfobox .username{padding-bottom:20px;font-weight:bold}.userinfobox td.label{text-align:right;white-space:nowrap;vertical-align:top;font-weight:bold}.groupinfobox{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);border-color:#e3e3e3}.groupinfobox blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}.groupinfobox .left{padding:10px;width:100px;vertical-align:top}.course-participation #showall{text-align:center;margin:10px 0}#user-policy .noticebox{text-align:center;margin-left:auto;margin-right:auto;margin-bottom:10px;width:80%;height:250px}#user-policy #policyframe{width:100%;height:100%}.iplookup #map{margin:auto}.userselector select{width:100%}.userselector div{margin-top:.2em}.userselector div label{margin-right:.3em}.userselector .userselector-infobelow{font-size:.8em}#userselector_options{padding:.3em 0}#userselector_options .collapsibleregioncaption{font-weight:bold}#userselector_options p{margin:.2em 0;text-align:left}.dir-rtl #userselector_options p{text-align:right}#page-user-profile .messagebox{text-align:center;margin-left:auto;margin-right:auto}#page-course-view-weeks .messagebox{text-align:center;margin-left:auto;margin-right:auto}.dir-rtl .userlist table#participants td,.dir-rtl .userlist table#participants th{text-align:right}.dir-rtl .userlist table#participants{margin:0 auto}#page-my-index.dir-rtl .block h3{text-align:right}.profileeditor>.singleselect{margin:0 .5em 0 0}.profileeditor>.singlebutton{display:inline-block;margin:0 0 0 .5em}.profileeditor>.singlebutton div,.profileeditor>.singlebutton input{margin:0}.dir-rtl .profileeditor>.singleselect{margin:0 0 0 .5em}.dir-rtl .profileeditor>.singlebutton{margin:0 .5em 0 0}#groupeditform .groups,#groupeditform .members{width:49%;float:left;text-align:left}.dir-rtl #groupeditform .groups,.dir-rtl #groupeditform .members{float:right;text-align:right}.preferences-group ul{list-style:none;margin-left:0;margin-right:0}.dir-rtl .preferences-group{float:right}.search-results .result{margin-left:0;margin-right:0}.dir-rtl .search-results .result{margin-right:15px;margin-left:0}.search-results .result .result-content{margin:7px 0}.search-input-wrapper{margin:0 5px 0 2px;overflow:hidden;float:right;height:100%;width:16px;transition:width .5s ease,left .5s ease}.search-input-wrapper>div{float:left;margin:9px 0 10px 0}.dir-rtl .search-input-wrapper{margin:0 2px 0 5px;float:left}.dir-rtl .search-input-wrapper>div{float:right}.dir-rtl .search-input-wrapper>form{margin:7px 25px 7px 0}.search-input-wrapper>form{opacity:0;margin:7px 0 7px 25px;transition:opacity .5s ease-in-out}.search-input-wrapper>form>input{margin:0}.search-input-wrapper form.expanded{opacity:1}.search-input-wrapper.expanded{width:158px}/*!
+.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-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}.content-only #region-main-box,.content-only #region-main{width:100%}.empty-region-side-pre.used-region-side-post #region-main{width:100%}.empty-region-side-post.used-region-side-pre #region-main-box{width:100%}.jsenabled.docked-region-side-pre.empty-region-side-pre.used-region-side-post #region-main{width:100%}.jsenabled.docked-region-side-post.empty-region-side-post.used-region-side-pre #region-main-box{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.46808511%;*width:74.41489362%}.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.40425532%;*width:23.35106383%}.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{list-style:none;margin:0;padding:0}.inline,.inline-list li{display:inline}.notifytiny{font-size:10.5px}.notifytiny li,.notifytiny td{font-size:100%}.red,.notifyprobl