Merge branch 'MDL-52811_prevent_forcelang' of git://github.com/davosmith/moodle
authorDavid Monllao <davidm@moodle.com>
Tue, 6 Feb 2018 07:00:11 +0000 (08:00 +0100)
committerDavid Monllao <davidm@moodle.com>
Tue, 6 Feb 2018 07:00:11 +0000 (08:00 +0100)
99 files changed:
admin/tool/usertours/amd/build/tour.min.js
admin/tool/usertours/amd/src/tour.js
admin/tool/usertours/thirdpartylibs.xml
auth/ldap/auth.php
backup/moodle2/restore_stepslib.php
backup/util/dbops/restore_dbops.class.php
backup/util/helper/restore_questions_parser_processor.class.php
blocks/blog_tags/edit_form.php
blocks/blog_tags/lang/en/block_blog_tags.php
blocks/html/lang/en/block_html.php
blocks/html/tests/behat/configuring_html_block.feature
blocks/html/tests/behat/course_block.feature
blocks/html/tests/behat/multiple_instances.feature
blocks/mentees/lang/en/block_mentees.php
blocks/mentees/tests/behat/configuring_mentees_block.feature [new file with mode: 0644]
blocks/myoverview/tests/behat/block_myoverview_dashboard.feature
blocks/tag_flickr/lang/en/block_tag_flickr.php
blocks/tag_flickr/tests/behat/configuring_tag_flickr_block.feature [new file with mode: 0644]
blocks/tag_youtube/lang/en/block_tag_youtube.php
blocks/tags/lang/en/block_tags.php
blocks/tests/behat/configure_block_throughout_site.feature
calendar/externallib.php
calendar/lib.php
calendar/tests/externallib_test.php
calendar/upgrade.txt
composer.json
composer.lock
course/tests/behat/general_section.feature [new file with mode: 0644]
course/tests/behat/keyholder.feature [new file with mode: 0644]
enrol/locallib.php
install/lang/an/admin.php [new file with mode: 0644]
install/lang/an/moodle.php [new file with mode: 0644]
lang/en/admin.php
lang/en/blog.php
lang/en/moodle.php
lang/en/question.php
lib/amd/build/chart_output_chartjs.min.js
lib/amd/src/chart_output_chartjs.js
lib/classes/message/message.php
lib/classes/output/icon_system_fontawesome.php
lib/db/services.php
lib/db/upgrade.php
lib/deprecatedlib.php
lib/filestorage/stored_file.php
lib/questionlib.php
lib/tests/message_test.php
lib/tests/questionlib_test.php
mod/assign/locallib.php
mod/assign/submission/onlinetext/locallib.php
mod/chat/classes/external.php
mod/chat/classes/external/chat_message_exporter.php [new file with mode: 0644]
mod/chat/db/services.php
mod/chat/lib.php
mod/chat/report.php
mod/chat/tests/externallib_test.php
mod/chat/version.php
mod/chat/view.php
mod/forum/tests/behat/edit_post_student.feature
mod/glossary/tests/behat/import_entries.feature [new file with mode: 0644]
mod/glossary/tests/fixtures/texfilter_glossary_en.xml [new file with mode: 0644]
mod/quiz/addrandom.php
mod/quiz/addrandomform.php
mod/quiz/overrides.php
mod/quiz/tests/behat/quiz_user_override.feature [new file with mode: 0644]
question/amd/build/edit_tags.min.js [new file with mode: 0644]
question/amd/build/repository.min.js [new file with mode: 0644]
question/amd/build/selectors.min.js [new file with mode: 0644]
question/amd/src/edit_tags.js [new file with mode: 0644]
question/amd/src/repository.js [new file with mode: 0644]
question/amd/src/selectors.js [new file with mode: 0644]
question/category_class.php
question/category_form.php
question/classes/bank/search/category_condition.php
question/classes/bank/tags_action_column.php [new file with mode: 0644]
question/classes/bank/view.php
question/classes/external.php
question/editlib.php
question/engine/renderer.php
question/export_form.php
question/format.php
question/lib.php [new file with mode: 0644]
question/tests/behat/copy_questions.feature
question/tests/behat/question_categories.feature
question/tests/generator/lib.php
question/tests/generator_test.php
question/type/edit_question_form.php
question/type/random/edit_random_form.php
question/type/random/lang/en/qtype_random.php
question/type/random/questiontype.php
question/type/tags_form.php [new file with mode: 0644]
repository/equella/callback.php
search/engine/solr/classes/engine.php
theme/boost/scss/moodle/forms.scss
theme/boost/scss/moodle/tool_usertours.scss
user/lib.php
user/message.html
user/portfolio.php
user/profile/field/datetime/field.class.php
version.php

index 78eb5cf..f4b8763 100644 (file)
Binary files a/admin/tool/usertours/amd/build/tour.min.js and b/admin/tool/usertours/amd/build/tour.min.js differ
index f77f4d9..61e6cf4 100644 (file)
@@ -619,43 +619,44 @@ Tour.prototype.addEventHandler = function (eventName, handler) {
  */
 Tour.prototype.processStepListeners = function (stepConfig) {
     this.listeners.push(
-        // Next/Previous buttons.
-        {
-            node: this.currentStepNode,
-            args: ['click', '[data-role="next"]', $.proxy(this.next, this)]
-        }, {
-            node: this.currentStepNode,
-            args: ['click', '[data-role="previous"]', $.proxy(this.previous, this)]
-        },
-
-        // Close and end tour buttons.
-        {
-            node: this.currentStepNode,
-            args: ['click', '[data-role="end"]', $.proxy(this.endTour, this)]
-        },
-
-        // Click backdrop and hide tour.
-        {
-            node: $('[data-flexitour="backdrop"]'),
-            args: ['click', $.proxy(this.hide, this)]
-        },
-
-        // Click out and hide tour without backdrop.
-        {
-            node: $('body'),
-            args: ['click', $.proxy(function (e) {
-                // Handle click in or click out tour content,
-                // if click out, hide tour.
-                if (!this.currentStepNode.is(e.target) && $(e.target).closest('[data-role="flexitour-step"]').length === 0) {
-                    this.hide();
-                }}, this)]
-        },
+    // Next/Previous buttons.
+    {
+        node: this.currentStepNode,
+        args: ['click', '[data-role="next"]', $.proxy(this.next, this)]
+    }, {
+        node: this.currentStepNode,
+        args: ['click', '[data-role="previous"]', $.proxy(this.previous, this)]
+    },
+
+    // Close and end tour buttons.
+    {
+        node: this.currentStepNode,
+        args: ['click', '[data-role="end"]', $.proxy(this.endTour, this)]
+    },
+
+    // Click backdrop and hide tour.
+    {
+        node: $('[data-flexitour="backdrop"]'),
+        args: ['click', $.proxy(this.hide, this)]
+    },
+
+    // Click out and hide tour without backdrop.
+    {
+        node: $('body'),
+        args: ['click', $.proxy(function (e) {
+            // Handle click in or click out tour content,
+            // if click out, hide tour.
+            if (!this.currentStepNode.is(e.target) && $(e.target).closest('[data-role="flexitour-step"]').length === 0) {
+                this.hide();
+            }
+        }, this)]
+    },
 
-        // Keypresses.
-        {
-            node: $('body'),
-            args: ['keydown', $.proxy(this.handleKeyDown, this)]
-        });
+    // Keypresses.
+    {
+        node: $('body'),
+        args: ['keydown', $.proxy(this.handleKeyDown, this)]
+    });
 
     if (stepConfig.moveOnClick) {
         var targetNode = this.getStepTarget(stepConfig);
@@ -1260,6 +1261,58 @@ Tour.prototype.positionStep = function (stepConfig) {
             arrow: {
                 element: '[data-role="arrow"]'
             }
+        },
+        onCreate: function onCreate(data) {
+            recalculateArrowPosition(data);
+        },
+        onUpdate: function onUpdate(data) {
+            recalculateArrowPosition(data);
+        }
+    };
+
+    var recalculateArrowPosition = function recalculateArrowPosition(data) {
+        var placement = data.placement.split('-')[0];
+        var isVertical = ['left', 'right'].indexOf(placement) !== -1;
+        var arrowElement = data.instance.popper.querySelector('[data-role="arrow"]');
+        var stepElement = $(data.instance.popper.querySelector('[data-role="flexitour-step"]'));
+        if (isVertical) {
+            var arrowHeight = parseFloat(window.getComputedStyle(arrowElement).height);
+            var arrowOffset = parseFloat(window.getComputedStyle(arrowElement).top);
+            var popperHeight = parseFloat(window.getComputedStyle(data.instance.popper).height);
+            var popperOffset = parseFloat(window.getComputedStyle(data.instance.popper).top);
+            var popperBorderWidth = parseFloat(stepElement.css('borderTopWidth'));
+            var popperBorderRadiusWidth = parseFloat(stepElement.css('borderTopLeftRadius')) * 2;
+            var arrowPos = arrowOffset + arrowHeight / 2;
+            var maxPos = popperHeight + popperOffset - popperBorderWidth - popperBorderRadiusWidth;
+            var minPos = popperOffset + popperBorderWidth + popperBorderRadiusWidth;
+            if (arrowPos >= maxPos || arrowPos <= minPos) {
+                var newArrowPos = 0;
+                if (arrowPos > popperHeight / 2) {
+                    newArrowPos = maxPos - arrowHeight;
+                } else {
+                    newArrowPos = minPos + arrowHeight;
+                }
+                $(arrowElement).css('top', newArrowPos);
+            }
+        } else {
+            var arrowWidth = parseFloat(window.getComputedStyle(arrowElement).width);
+            var _arrowOffset = parseFloat(window.getComputedStyle(arrowElement).left);
+            var popperWidth = parseFloat(window.getComputedStyle(data.instance.popper).width);
+            var _popperOffset = parseFloat(window.getComputedStyle(data.instance.popper).left);
+            var _popperBorderWidth = parseFloat(stepElement.css('borderTopWidth'));
+            var _popperBorderRadiusWidth = parseFloat(stepElement.css('borderTopLeftRadius')) * 2;
+            var _arrowPos = _arrowOffset + arrowWidth / 2;
+            var _maxPos = popperWidth + _popperOffset - _popperBorderWidth - _popperBorderRadiusWidth;
+            var _minPos = _popperOffset + _popperBorderWidth + _popperBorderRadiusWidth;
+            if (_arrowPos >= _maxPos || _arrowPos <= _minPos) {
+                var _newArrowPos = 0;
+                if (_arrowPos > popperWidth / 2) {
+                    _newArrowPos = _maxPos - arrowWidth;
+                } else {
+                    _newArrowPos = _minPos + arrowWidth;
+                }
+                $(arrowElement).css('left', _newArrowPos);
+            }
         }
     };
 
index f55d8b1..11359bc 100644 (file)
@@ -4,7 +4,7 @@
     <location>amd/src/tour.js</location>
     <name>Flexitour</name>
     <license>GPLv3</license>
-    <version>0.12.0</version>
+    <version>0.12.2</version>
     <licenseversion>3</licenseversion>
   </library>
   <library>
index b923003..e7b5eb7 100644 (file)
@@ -1083,7 +1083,9 @@ class auth_plugin_ldap extends auth_plugin_base {
      *
      */
     function user_update($olduser, $newuser) {
-        global $USER;
+        global $CFG;
+
+        require_once($CFG->dirroot . '/user/profile/lib.php');
 
         if (isset($olduser->username) and isset($newuser->username) and $olduser->username != $newuser->username) {
             error_log($this->errorlogtag.get_string('renamingnotallowed', 'auth_ldap'));
index 1435d5e..6e050ab 100644 (file)
@@ -4400,6 +4400,21 @@ class restore_create_categories_and_questions extends restore_structure_step {
         }
         $data->contextid = $mapping->parentitemid;
 
+        // Before 3.5, question categories could be created at top level.
+        // From 3.5 onwards, all question categories should be a child of a special category called the "top" category.
+        $backuprelease = floatval($this->get_task()->get_info()->backup_release);
+        preg_match('/(\d{8})/', $this->get_task()->get_info()->moodle_release, $matches);
+        $backupbuild = (int)$matches[1];
+        $before35 = false;
+        if ($backuprelease < 3.5 || $backupbuild < 20180205) {
+            $before35 = true;
+        }
+        if (empty($mapping->info->parent) &&
+                ($before35 || $mapping->info->contextlevel == CONTEXT_MODULE)) {
+            $top = question_get_top_category($data->contextid, true);
+            $data->parent = $top->id;
+        }
+
         // Before 3.1, the 'stamp' field could be erroneously duplicated.
         // From 3.1 onwards, there's a unique index of (contextid, stamp).
         // If we encounter a duplicate in an old restore file, just generate a new stamp.
@@ -4560,7 +4575,6 @@ class restore_create_categories_and_questions extends restore_structure_step {
                      'backupid' => $this->get_restoreid(),
                      'itemname' => 'question_category_created'));
         foreach ($qcats as $qcat) {
-            $newparent = 0;
             $dbcat = $DB->get_record('question_categories', array('id' => $qcat->newitemid));
             // Get new parent (mapped or created, so we look in quesiton_category mappings)
             if ($newparent = $DB->get_field('backup_ids_temp', 'newitemid', array(
@@ -4576,8 +4590,11 @@ class restore_create_categories_and_questions extends restore_structure_step {
                 }
             }
             // Here with $newparent empty, problem with contexts or remapping, set it to top cat
-            if (!$newparent) {
-                $DB->set_field('question_categories', 'parent', 0, array('id' => $dbcat->id));
+            if (!$newparent && $dbcat->parent) {
+                $topcat = question_get_top_category($dbcat->contextid, true);
+                if ($dbcat->parent != $topcat->id) {
+                    $DB->set_field('question_categories', 'parent', $topcat->id, array('id' => $dbcat->id));
+                }
             }
         }
 
@@ -4586,7 +4603,6 @@ class restore_create_categories_and_questions extends restore_structure_step {
                   'backupid' => $this->get_restoreid(),
                   'itemname' => 'question_created'));
         foreach ($qs as $q) {
-            $newparent = 0;
             $dbq = $DB->get_record('question', array('id' => $q->newitemid));
             // Get new parent (mapped or created, so we look in question mappings)
             if ($newparent = $DB->get_field('backup_ids_temp', 'newitemid', array(
@@ -4615,18 +4631,38 @@ class restore_move_module_questions_categories extends restore_execution_step {
     protected function define_execution() {
         global $DB;
 
+        $backuprelease = floatval($this->task->get_info()->backup_release);
+        preg_match('/(\d{8})/', $this->task->get_info()->moodle_release, $matches);
+        $backupbuild = (int)$matches[1];
+        $before35 = false;
+        if ($backuprelease < 3.5 || $backupbuild < 20180205) {
+            $before35 = true;
+        }
+
         $contexts = restore_dbops::restore_get_question_banks($this->get_restoreid(), CONTEXT_MODULE);
         foreach ($contexts as $contextid => $contextlevel) {
             // Only if context mapping exists (i.e. the module has been restored)
             if ($newcontext = restore_dbops::get_backup_ids_record($this->get_restoreid(), 'context', $contextid)) {
                 // Update all the qcats having their parentitemid set to the original contextid
-                $modulecats = $DB->get_records_sql("SELECT itemid, newitemid
+                $modulecats = $DB->get_records_sql("SELECT itemid, newitemid, info
                                                       FROM {backup_ids_temp}
                                                      WHERE backupid = ?
                                                        AND itemname = 'question_category'
                                                        AND parentitemid = ?", array($this->get_restoreid(), $contextid));
                 foreach ($modulecats as $modulecat) {
-                    $DB->set_field('question_categories', 'contextid', $newcontext->newitemid, array('id' => $modulecat->newitemid));
+                    $cat = new stdClass();
+                    $cat->id = $modulecat->newitemid;
+                    $cat->contextid = $newcontext->newitemid;
+
+                    // Before 3.5, question categories could be created at top level.
+                    // From 3.5 onwards, all question categories should be a child of a special category called the "top" category.
+                    $info = backup_controller_dbops::decode_backup_temp_info($modulecat->info);
+                    if ($before35 && empty($info->parent)) {
+                        $top = question_get_top_category($newcontext->newitemid, true);
+                        $cat->parent = $top->id;
+                    }
+                    $DB->update_record('question_categories', $cat);
+
                     // And set new contextid also in question_category mapping (will be
                     // used by {@link restore_create_question_files} later
                     restore_dbops::set_backup_ids_record($this->get_restoreid(), 'question_category', $modulecat->itemid, $modulecat->newitemid, $newcontext->newitemid);
index a4c7075..ec45776 100644 (file)
@@ -558,9 +558,16 @@ abstract class restore_dbops {
      *
      * The function returns 2 arrays, one containing errors and another containing
      * warnings. Both empty if no errors/warnings are found.
+     *
+     * @param int $restoreid The restore ID
+     * @param int $courseid The ID of the course
+     * @param int $userid The id of the user doing the restore
+     * @param bool $samesite True if restore is to same site
+     * @param int $contextlevel (CONTEXT_SYSTEM, etc.)
+     * @return array A separate list of all error and warnings detected
      */
     public static function prechek_precheck_qbanks_by_level($restoreid, $courseid, $userid, $samesite, $contextlevel) {
-        global $CFG, $DB;
+        global $DB;
 
         // To return any errors and warnings found
         $errors   = array();
@@ -571,6 +578,17 @@ abstract class restore_dbops {
             CONTEXT_SYSTEM => CONTEXT_COURSE,
             CONTEXT_COURSECAT => CONTEXT_COURSE);
 
+        $rc = restore_controller_dbops::load_controller($restoreid);
+        $restoreinfo = $rc->get_info();
+        $rc->destroy(); // Always need to destroy.
+        $backuprelease = floatval($restoreinfo->backup_release);
+        preg_match('/(\d{8})/', $restoreinfo->moodle_release, $matches);
+        $backupbuild = (int)$matches[1];
+        $after35 = false;
+        if ($backuprelease >= 3.5 && $backupbuild > 20180205) {
+            $after35 = true;
+        }
+
         // For any contextlevel, follow this process logic:
         //
         // 0) Iterate over each context (qbank)
@@ -587,6 +605,7 @@ abstract class restore_dbops {
         //                 7a) There is fallback, move ALL the qcats to fallback, warn. End qcat loop
         //                 7b) No fallback, error. End qcat loop
         //         5b) Match, mark q to be mapped
+        // 8) Check if backup is from Moodle >= 3.5 and error if more than one top-level category in the context.
 
         // Get all the contexts (question banks) in restore for the given contextlevel
         $contexts = self::restore_get_question_banks($restoreid, $contextlevel);
@@ -596,6 +615,8 @@ abstract class restore_dbops {
             // Init some perms
             $canmanagecategory = false;
             $canadd            = false;
+            // Top-level category counter.
+            $topcats = 0;
             // get categories in context (bank)
             $categories = self::restore_get_question_categories($restoreid, $contextid);
             // cache permissions if $targetcontext is found
@@ -605,6 +626,10 @@ abstract class restore_dbops {
             }
             // 1) Iterate over each qcat in the context, matching by stamp for the found target context
             foreach ($categories as $category) {
+                if ($category->parent == 0) {
+                    $topcats++;
+                }
+
                 $matchcat = false;
                 if ($targetcontext) {
                     $matchcat = $DB->get_record('question_categories', array(
@@ -690,6 +715,12 @@ abstract class restore_dbops {
                     }
                 }
             }
+
+            // 8) Check if backup is made on Moodle >= 3.5 and there are more than one top-level category in the context.
+            if ($after35 && $topcats > 1) {
+                $errors[] = get_string('restoremultipletopcats', 'questions', $contextid);
+            }
+
         }
 
         return array($errors, $warnings);
index 4e94468..129b2f4 100644 (file)
@@ -26,7 +26,7 @@ require_once($CFG->dirroot.'/backup/util/xml/parser/processors/grouped_parser_pr
 
 /**
  * helper implementation of grouped_parser_processor that will
- * load all the categories and questions (header info only) from then questions.xml file
+ * load all the categories and questions (header info only) from the questions.xml file
  * to the backup_ids table storing the whole structure there for later processing.
  * Note: only "needed" categories are loaded (must have question_categoryref record in backup_ids)
  * Note: parentitemid will contain the category->contextid for categories
index 6f55551..581e0b4 100644 (file)
@@ -33,7 +33,7 @@ class block_blog_tags_edit_form extends block_edit_form {
         // Fields for editing HTML block title and contents.
         $mform->addElement('header', 'configheader', get_string('blocksettings', 'block'));
 
-        $mform->addElement('text', 'config_title', get_string('blocktitle', 'blog'));
+        $mform->addElement('text', 'config_title', get_string('configtitle', 'block_blog_tags'));
         $mform->setDefault('config_title', get_string('blogtags', 'blog'));
         $mform->setType('config_title', PARAM_TEXT);
 
index 408e190..e5c6bc2 100644 (file)
@@ -24,3 +24,4 @@
 
 $string['blog_tags:addinstance'] = 'Add a new blog tags block';
 $string['pluginname'] = 'Blog tags';
+$string['configtitle'] = 'Blog tags block title';
index 7eb2ad4..cdf9bb1 100644 (file)
@@ -27,7 +27,7 @@ $string['configallowadditionalcssclasses'] = 'Adds a configuration option to HTM
 $string['configclasses'] = 'Additional CSS classes';
 $string['configclasses_help'] = 'The purpose of this configuration is to aid with theming by helping distinguish HTML blocks from each other. Any CSS classes entered here (space delimited) will be appended to the block\'s default classes.';
 $string['configcontent'] = 'Content';
-$string['configtitle'] = 'Block title';
+$string['configtitle'] = 'HTML block title';
 $string['html:addinstance'] = 'Add a new HTML block';
 $string['html:myaddinstance'] = 'Add a new HTML block to Dashboard';
 $string['leaveblanktohide'] = 'leave blank to hide the title';
index b4865e7..05a00d9 100644 (file)
@@ -12,10 +12,11 @@ Feature: Adding and configuring HTML blocks
     And I add the "HTML" block
     And I configure the "(new HTML block)" block
     And I set the field "Content" to "Static text without a header"
+    Then I should see "HTML block title"
     And I press "Save changes"
     Then I should not see "(new HTML block)"
     And I configure the "block_html" block
-    And I set the field "Block title" to "The HTML block header"
+    And I set the field "HTML block title" to "The HTML block header"
     And I set the field "Content" to "Static text with a header"
     And I press "Save changes"
     And "block_html" "block" should exist
@@ -32,7 +33,7 @@ Feature: Adding and configuring HTML blocks
     And I press "Save changes"
     Then I should not see "(new HTML block)"
     And I configure the "block_html" block
-    And I set the field "Block title" to "The HTML block header"
+    And I set the field "HTML block title" to "The HTML block header"
     And I set the field "Content" to "Static text with a header"
     And I press "Save changes"
     And "block_html" "block" should exist
index 08e233e..3341177 100644 (file)
@@ -21,15 +21,15 @@ Feature: HTML blocks in a course
     And I add the "HTML" block
     And I configure the "(new HTML block)" block
     And I set the field "Content" to "First block content"
-    And I set the field "Block title" to "First block header"
+    And I set the field "HTML block title" to "First block header"
     And I press "Save changes"
     And I add the "HTML" block
     And I configure the "(new HTML block)" block
     And I set the field "Content" to "Second block content"
-    And I set the field "Block title" to "Second block header"
+    And I set the field "HTML block title" to "Second block header"
     And I press "Save changes"
     And I log out
     And I log in as "student1"
     And I am on "Course 1" course homepage
     And I should see "First block content" in the "First block header" "block"
-    And I should see "Second block content" in the "Second block header" "block"
\ No newline at end of file
+    And I should see "Second block content" in the "Second block header" "block"
index a6cad0b..3ed187b 100644 (file)
@@ -29,12 +29,12 @@ Feature: Adding and configuring multiple HTML blocks
 
   Scenario: Adding multiple instances of HTML block on a page
     And I configure the "block_html" block
-    And I set the field "Block title" to "The HTML block header"
+    And I set the field "HTML block title" to "The HTML block header"
     And I set the field "Content" to "Static text with a header"
     And I press "Save changes"
     And I add the "HTML" block
     And I configure the "(new HTML block)" block
-    And I set the field "Block title" to "The second HTML block header"
+    And I set the field "HTML block title" to "The second HTML block header"
     And I set the field "Content" to "Second block contents"
     And I press "Save changes"
     And I log out
index 3f3389b..634f1e3 100644 (file)
@@ -23,8 +23,8 @@
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
-$string['configtitle'] = 'Block title';
-$string['configtitleblankhides'] = 'Block title (no title if blank)';
+$string['configtitle'] = 'Mentees block title';
+$string['configtitleblankhides'] = 'Mentees block title (no title if blank)';
 $string['leaveblanktohide'] = 'leave blank to hide the title';
 $string['mentees:addinstance'] = 'Add a new mentees block';
 $string['mentees:myaddinstance'] = 'Add a new mentees block to Dashboard';
diff --git a/blocks/mentees/tests/behat/configuring_mentees_block.feature b/blocks/mentees/tests/behat/configuring_mentees_block.feature
new file mode 100644 (file)
index 0000000..3b6df26
--- /dev/null
@@ -0,0 +1,18 @@
+@block @block_mentees @core_block
+Feature: Adding and configuring Mentees blocks
+  In order to have a Mentees blocks on a page
+  As admin
+  I need to be able to insert and configure a Mentees blocks
+
+  @javascript
+  Scenario: Configuring the Mentees block with Javascript on
+    Given I log in as "admin"
+    And I am on site homepage
+    When I turn editing mode on
+    And I add the "Mentees" block
+    And I configure the "(new Mentees block)" block
+    Then I should see "Mentees block title (no title if blank)"
+    And I set the field "Mentees block title (no title if blank)" to "The Mentees block header"
+    And I press "Save changes"
+    And "block_mentees" "block" should exist
+    Then "The Mentees block header" "block" should exist
index fbaea9f..bf6f356 100644 (file)
@@ -18,9 +18,9 @@ Feature: The my overview block allows users to easily access their courses and s
       | activity | course | idnumber  | name            | intro                   | timeopen      | timeclose     |
       | choice   | C2     | choice1   | Test choice 1   | Test choice description | ##yesterday## | ##tomorrow##  |
       | choice   | C1     | choice2   | Test choice 2   | Test choice description | ##1 month ago## | ##15 days ago##  |
-      | choice   | C3     | choice3   | Test choice 3   | Test choice description | ##first day of next month## | ##last day of next month##  |
+      | choice   | C3     | choice3   | Test choice 3   | Test choice description | ##first day of +5 months## | ##last day of +5 months##  |
       | feedback | C2     | feedback1 | Test feedback 1 | Test feedback description | ##yesterday## | ##tomorrow##  |
-      | feedback | C3     | feedback3 | Test feedback 3 | Test feedback description | ##first day of next month## | ##last day of next month## |
+      | feedback | C3     | feedback3 | Test feedback 3 | Test feedback description | ##first day of +5 months## | ##last day of +5 months## |
     And the following "course enrolments" exist:
       | user | course | role |
       | student1 | C1 | student |
index 1057087..8a1e4a4 100644 (file)
@@ -22,7 +22,7 @@
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
-$string['configtitle'] = 'Title';
+$string['configtitle'] = 'Flickr block title';
 $string['date-posted-asc'] = 'Date posted ASC';
 $string['date-posted-desc'] = 'Date posted DESC';
 $string['date-taken-asc'] = 'Date taken ASC';
diff --git a/blocks/tag_flickr/tests/behat/configuring_tag_flickr_block.feature b/blocks/tag_flickr/tests/behat/configuring_tag_flickr_block.feature
new file mode 100644 (file)
index 0000000..f213f66
--- /dev/null
@@ -0,0 +1,20 @@
+@block @block_tag_flickr
+Feature: Adding and configuring Flickr block
+  In order to have the Flickr block used
+  As a admin
+  I need to add the Flickr block to the tags site page
+
+  @javascript
+  Scenario: Adding Flickr block to the tags site page
+    Given I log in as "admin"
+    And I press "Customise this page"
+    # TODO MDL-57120 site "Tags" link not accessible without navigation block.
+    And I add the "Navigation" block if not present
+    And I navigate to "Tags" node in "Site pages"
+    And I add the "Flickr" block
+    And I configure the "Flickr" block
+    Then I should see "Flickr block title"
+    And I set the field "Flickr block title" to "The Flickr block header"
+    And I press "Save changes"
+    And "block_tag_flickr" "block" should exist
+    Then "The Flickr block header" "block" should exist
index 2016fd3..50df4cc 100644 (file)
@@ -29,7 +29,7 @@ $string['apikeyinfo'] = 'Get a <a href="https://developers.google.com/youtube/v3
 $string['autosvehicles'] = 'Autos &amp; Vehicles';
 $string['category'] = 'Category';
 $string['comedy'] = 'Comedy';
-$string['configtitle'] = 'Title';
+$string['configtitle'] = 'YouTube block title';
 $string['education'] = 'Education';
 $string['entertainment'] = 'Entertainment';
 $string['filmsanimation'] = 'Films &amp; Animation';
index 5fce06e..457dcfe 100644 (file)
@@ -24,7 +24,7 @@
 
 $string['anycollection'] = 'Any';
 $string['anytype'] = 'All';
-$string['configtitle'] = 'Block title';
+$string['configtitle'] = 'Tags block title';
 $string['disabledtags'] = 'Tags are disabled';
 $string['defaultdisplay'] = 'Display tags';
 $string['pluginname'] = 'Tags';
index d548f60..84fefa7 100644 (file)
@@ -67,7 +67,7 @@ Feature: Add and configure blocks throughout the site
     And I add the "HTML" block
     And I configure the "(new HTML block)" block
     And I set the following fields to these values:
-      | Block title | Foo " onload="document.getElementsByTagName('body')[0].remove()" alt=" |
+      | HTML block title | Foo " onload="document.getElementsByTagName('body')[0].remove()" alt=" |
       | Content     | Example |
     When I press "Save changes"
     Then I should see "Course overview"
index 0aeb9a4..e644e39 100644 (file)
@@ -356,6 +356,8 @@ class core_calendar_external extends external_api {
                             'description' => new external_value(PARAM_RAW, 'Description', VALUE_OPTIONAL, null, NULL_ALLOWED),
                             'format' => new external_format_value('description'),
                             'courseid' => new external_value(PARAM_INT, 'course id'),
+                            'categoryid' => new external_value(PARAM_INT, 'Category id (only for category events).',
+                                VALUE_OPTIONAL),
                             'groupid' => new external_value(PARAM_INT, 'group id'),
                             'userid' => new external_value(PARAM_INT, 'user id'),
                             'repeatid' => new external_value(PARAM_INT, 'repeat id'),
index 6be5dc5..0879883 100644 (file)
@@ -808,9 +808,6 @@ class calendar_event {
                     \coursecat::get($properties->categoryid, MUST_EXIST, true);
                     // Course context.
                     $this->editorcontext = $this->get_context();
-                    // We have a course and are within the course context so we had
-                    // better use the courses max bytes value.
-                    $this->editoroptions['maxbytes'] = $course->maxbytes;
                 } else {
                     // If we get here we have a custom event type as used by some
                     // modules. In this case the event will have been added by
index 42e3b0e..9fcf147 100644 (file)
@@ -506,6 +506,7 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase {
         // Should be just one, since there's just one category event of the course I am enrolled (course3 - cat2b).
         $this->assertEquals(1, count($events['events']));
         $this->assertEquals($catevent2->id, $events['events'][0]['id']);
+        $this->assertEquals($category2->id, $events['events'][0]['categoryid']);
         $this->assertEquals(0, count($events['warnings']));
 
         // Now get category events but by course (there aren't course events in the course).
@@ -536,7 +537,9 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase {
         $this->assertEquals(2, count($events['events']));
         $this->assertEquals(0, count($events['warnings']));
         $this->assertEquals($catevent1->id, $events['events'][0]['id']);
+        $this->assertEquals($category->id, $events['events'][0]['categoryid']);
         $this->assertEquals($catevent2->id, $events['events'][1]['id']);
+        $this->assertEquals($category2->id, $events['events'][1]['categoryid']);
     }
 
     /**
index b2973e5..359ee3e 100644 (file)
@@ -1,6 +1,9 @@
 This files describes API changes in /calendar/* ,
 information provided here is intended especially for developers.
 
+=== 3.5 ===
+* core_calendar_external::get_calendar_events now returns the categoryid for category events.
+
 === 3.4 ===
 * calendar_get_mini, and calendar_get_upcoming have been deprecated. Please update to use the new exporters and renderers.
 * added core_calendar_get_valid_event_timestart_range and core_calendar_event_timestart_updated callbacks for module events
index 3dd8de4..70c46b7 100644 (file)
@@ -1,13 +1,13 @@
 {
     "name": "moodle/moodle",
-    "license": "GPL-3.0",
+    "license": "GPL-3.0-or-later",
     "description": "Moodle - the world's open source learning platform",
     "type": "project",
     "homepage": "https://moodle.org",
     "require-dev": {
         "phpunit/phpunit": "6.4.*",
         "phpunit/dbUnit": "3.0.*",
-        "moodlehq/behat-extension": "3.35.0",
+        "moodlehq/behat-extension": "3.35.1",
         "mikey179/vfsStream": "^1.6"
     }
 }
index f90ae23..b9fe34a 100644 (file)
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
         "This file is @generated automatically"
     ],
-    "content-hash": "7cd70172c941fb07f0a2d4173baef5f1",
+    "content-hash": "93454a669db9cfbc99f35f7bacd6ac82",
     "packages": [],
     "packages-dev": [
         {
         },
         {
             "name": "moodlehq/behat-extension",
-            "version": "v3.35.0",
+            "version": "v3.35.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/moodlehq/moodle-behat-extension.git",
-                "reference": "8d0c4248b1efe6bc141fc7dc17d16fed1df017a5"
+                "reference": "e6e92fd551185f73603bad5694e854f3f6906e0e"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/moodlehq/moodle-behat-extension/zipball/8d0c4248b1efe6bc141fc7dc17d16fed1df017a5",
-                "reference": "8d0c4248b1efe6bc141fc7dc17d16fed1df017a5",
+                "url": "https://api.github.com/repos/moodlehq/moodle-behat-extension/zipball/e6e92fd551185f73603bad5694e854f3f6906e0e",
+                "reference": "e6e92fd551185f73603bad5694e854f3f6906e0e",
                 "shasum": ""
             },
             "require": {
                 "Behat",
                 "moodle"
             ],
-            "time": "2017-09-29T18:10:58+00:00"
+            "time": "2018-01-24T14:09:40+00:00"
         },
         {
             "name": "myclabs/deep-copy",
         },
         {
             "name": "phpdocumentor/reflection-docblock",
-            "version": "4.1.1",
+            "version": "4.2.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
-                "reference": "2d3d238c433cf69caeb4842e97a3223a116f94b2"
+                "reference": "66465776cfc249844bde6d117abff1d22e06c2da"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/2d3d238c433cf69caeb4842e97a3223a116f94b2",
-                "reference": "2d3d238c433cf69caeb4842e97a3223a116f94b2",
+                "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/66465776cfc249844bde6d117abff1d22e06c2da",
+                "reference": "66465776cfc249844bde6d117abff1d22e06c2da",
                 "shasum": ""
             },
             "require": {
                 "php": "^7.0",
-                "phpdocumentor/reflection-common": "^1.0@dev",
+                "phpdocumentor/reflection-common": "^1.0.0",
                 "phpdocumentor/type-resolver": "^0.4.0",
                 "webmozart/assert": "^1.0"
             },
             "require-dev": {
-                "mockery/mockery": "^0.9.4",
-                "phpunit/phpunit": "^4.4"
+                "doctrine/instantiator": "~1.0.5",
+                "mockery/mockery": "^1.0",
+                "phpunit/phpunit": "^6.4"
             },
             "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "4.x-dev"
+                }
+            },
             "autoload": {
                 "psr-4": {
                     "phpDocumentor\\Reflection\\": [
                 }
             ],
             "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
-            "time": "2017-08-30T18:51:59+00:00"
+            "time": "2017-11-27T17:38:31+00:00"
         },
         {
             "name": "phpdocumentor/type-resolver",
         },
         {
             "name": "phpunit/dbunit",
-            "version": "3.0.2",
+            "version": "3.0.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/dbunit.git",
-                "reference": "403350339b6aca748ee0067d027d85621992e21f"
+                "reference": "0fa4329e490480ab957fe7b1185ea0996ca11f44"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/dbunit/zipball/403350339b6aca748ee0067d027d85621992e21f",
-                "reference": "403350339b6aca748ee0067d027d85621992e21f",
+                "url": "https://api.github.com/repos/sebastianbergmann/dbunit/zipball/0fa4329e490480ab957fe7b1185ea0996ca11f44",
+                "reference": "0fa4329e490480ab957fe7b1185ea0996ca11f44",
                 "shasum": ""
             },
             "require": {
                 "testing",
                 "xunit"
             ],
-            "time": "2017-11-18T17:40:34+00:00"
+            "time": "2018-01-23T13:32:26+00:00"
         },
         {
             "name": "phpunit/php-code-coverage",
-            "version": "5.2.3",
+            "version": "5.3.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
-                "reference": "8e1d2397d8adf59a3f12b2878a3aaa66d1ab189d"
+                "reference": "661f34d0bd3f1a7225ef491a70a020ad23a057a1"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/8e1d2397d8adf59a3f12b2878a3aaa66d1ab189d",
-                "reference": "8e1d2397d8adf59a3f12b2878a3aaa66d1ab189d",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/661f34d0bd3f1a7225ef491a70a020ad23a057a1",
+                "reference": "661f34d0bd3f1a7225ef491a70a020ad23a057a1",
                 "shasum": ""
             },
             "require": {
                 "php": "^7.0",
                 "phpunit/php-file-iterator": "^1.4.2",
                 "phpunit/php-text-template": "^1.2.1",
-                "phpunit/php-token-stream": "^2.0",
+                "phpunit/php-token-stream": "^2.0.1",
                 "sebastian/code-unit-reverse-lookup": "^1.0.1",
                 "sebastian/environment": "^3.0",
                 "sebastian/version": "^2.0.1",
                 "theseer/tokenizer": "^1.1"
             },
             "require-dev": {
-                "ext-xdebug": "^2.5",
                 "phpunit/phpunit": "^6.0"
             },
             "suggest": {
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "5.2.x-dev"
+                    "dev-master": "5.3.x-dev"
                 }
             },
             "autoload": {
             "authors": [
                 {
                     "name": "Sebastian Bergmann",
-                    "email": "sb@sebastian-bergmann.de",
+                    "email": "sebastian@phpunit.de",
                     "role": "lead"
                 }
             ],
                 "testing",
                 "xunit"
             ],
-            "time": "2017-11-03T13:47:33+00:00"
+            "time": "2017-12-06T09:29:45+00:00"
         },
         {
             "name": "phpunit/php-file-iterator",
-            "version": "1.4.3",
+            "version": "1.4.5",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
-                "reference": "8ebba84e5bd74fc5fdeb916b38749016c7232f93"
+                "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/8ebba84e5bd74fc5fdeb916b38749016c7232f93",
-                "reference": "8ebba84e5bd74fc5fdeb916b38749016c7232f93",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4",
+                "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4",
                 "shasum": ""
             },
             "require": {
                 "filesystem",
                 "iterator"
             ],
-            "time": "2017-11-24T15:00:59+00:00"
+            "time": "2017-11-27T13:52:08+00:00"
         },
         {
             "name": "phpunit/php-text-template",
         },
         {
             "name": "phpunit/php-token-stream",
-            "version": "2.0.1",
+            "version": "2.0.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/php-token-stream.git",
-                "reference": "9a02332089ac48e704c70f6cefed30c224e3c0b0"
+                "reference": "791198a2c6254db10131eecfe8c06670700904db"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/9a02332089ac48e704c70f6cefed30c224e3c0b0",
-                "reference": "9a02332089ac48e704c70f6cefed30c224e3c0b0",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/791198a2c6254db10131eecfe8c06670700904db",
+                "reference": "791198a2c6254db10131eecfe8c06670700904db",
                 "shasum": ""
             },
             "require": {
             "keywords": [
                 "tokenizer"
             ],
-            "time": "2017-08-20T05:47:52+00:00"
+            "time": "2017-11-27T05:48:46+00:00"
         },
         {
             "name": "phpunit/phpunit",
         },
         {
             "name": "sebastian/comparator",
-            "version": "2.1.0",
+            "version": "2.1.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/comparator.git",
-                "reference": "1174d9018191e93cb9d719edec01257fc05f8158"
+                "reference": "11c07feade1d65453e06df3b3b90171d6d982087"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/1174d9018191e93cb9d719edec01257fc05f8158",
-                "reference": "1174d9018191e93cb9d719edec01257fc05f8158",
+                "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/11c07feade1d65453e06df3b3b90171d6d982087",
+                "reference": "11c07feade1d65453e06df3b3b90171d6d982087",
                 "shasum": ""
             },
             "require": {
                 "compare",
                 "equality"
             ],
-            "time": "2017-11-03T07:16:52+00:00"
+            "time": "2018-01-12T06:34:42+00:00"
         },
         {
             "name": "sebastian/diff",
         },
         {
             "name": "symfony/browser-kit",
-            "version": "v3.3.13",
+            "version": "v3.4.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/browser-kit.git",
-                "reference": "03f957cd24bf939524f07b8b910c89cfcad722a8"
+                "reference": "490f27762705c8489bd042fe3e9377a191dba9b4"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/browser-kit/zipball/03f957cd24bf939524f07b8b910c89cfcad722a8",
-                "reference": "03f957cd24bf939524f07b8b910c89cfcad722a8",
+                "url": "https://api.github.com/repos/symfony/browser-kit/zipball/490f27762705c8489bd042fe3e9377a191dba9b4",
+                "reference": "490f27762705c8489bd042fe3e9377a191dba9b4",
                 "shasum": ""
             },
             "require": {
                 "php": "^5.5.9|>=7.0.8",
-                "symfony/dom-crawler": "~2.8|~3.0"
+                "symfony/dom-crawler": "~2.8|~3.0|~4.0"
             },
             "require-dev": {
-                "symfony/css-selector": "~2.8|~3.0",
-                "symfony/process": "~2.8|~3.0"
+                "symfony/css-selector": "~2.8|~3.0|~4.0",
+                "symfony/process": "~2.8|~3.0|~4.0"
             },
             "suggest": {
                 "symfony/process": ""
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.3-dev"
+                    "dev-master": "3.4-dev"
                 }
             },
             "autoload": {
             ],
             "description": "Symfony BrowserKit Component",
             "homepage": "https://symfony.com",
-            "time": "2017-11-07T14:12:55+00:00"
+            "time": "2018-01-03T07:37:34+00:00"
         },
         {
             "name": "symfony/class-loader",
-            "version": "v3.3.13",
+            "version": "v3.4.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/class-loader.git",
-                "reference": "df173ac2af96ce202bf8bb5a3fc0bec8a4fdd4d1"
+                "reference": "e63c12699822bb3b667e7216ba07fbcc3a3e203e"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/class-loader/zipball/df173ac2af96ce202bf8bb5a3fc0bec8a4fdd4d1",
-                "reference": "df173ac2af96ce202bf8bb5a3fc0bec8a4fdd4d1",
+                "url": "https://api.github.com/repos/symfony/class-loader/zipball/e63c12699822bb3b667e7216ba07fbcc3a3e203e",
+                "reference": "e63c12699822bb3b667e7216ba07fbcc3a3e203e",
                 "shasum": ""
             },
             "require": {
                 "php": "^5.5.9|>=7.0.8"
             },
             "require-dev": {
-                "symfony/finder": "~2.8|~3.0",
+                "symfony/finder": "~2.8|~3.0|~4.0",
                 "symfony/polyfill-apcu": "~1.1"
             },
             "suggest": {
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.3-dev"
+                    "dev-master": "3.4-dev"
                 }
             },
             "autoload": {
             ],
             "description": "Symfony ClassLoader Component",
             "homepage": "https://symfony.com",
-            "time": "2017-11-05T15:47:03+00:00"
+            "time": "2018-01-03T07:37:34+00:00"
         },
         {
             "name": "symfony/config",
-            "version": "v3.3.13",
+            "version": "v3.4.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/config.git",
-                "reference": "8d2649077dc54dfbaf521d31f217383d82303c5f"
+                "reference": "cfd5c972f7b4992a5df41673d25d980ab077aa5b"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/config/zipball/8d2649077dc54dfbaf521d31f217383d82303c5f",
-                "reference": "8d2649077dc54dfbaf521d31f217383d82303c5f",
+                "url": "https://api.github.com/repos/symfony/config/zipball/cfd5c972f7b4992a5df41673d25d980ab077aa5b",
+                "reference": "cfd5c972f7b4992a5df41673d25d980ab077aa5b",
                 "shasum": ""
             },
             "require": {
                 "php": "^5.5.9|>=7.0.8",
-                "symfony/filesystem": "~2.8|~3.0"
+                "symfony/filesystem": "~2.8|~3.0|~4.0"
             },
             "conflict": {
                 "symfony/dependency-injection": "<3.3",
                 "symfony/finder": "<3.3"
             },
             "require-dev": {
-                "symfony/dependency-injection": "~3.3",
-                "symfony/finder": "~3.3",
-                "symfony/yaml": "~3.0"
+                "symfony/dependency-injection": "~3.3|~4.0",
+                "symfony/finder": "~3.3|~4.0",
+                "symfony/yaml": "~3.0|~4.0"
             },
             "suggest": {
                 "symfony/yaml": "To use the yaml reference dumper"
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.3-dev"
+                    "dev-master": "3.4-dev"
                 }
             },
             "autoload": {
             ],
             "description": "Symfony Config Component",
             "homepage": "https://symfony.com",
-            "time": "2017-11-07T14:16:22+00:00"
+            "time": "2018-01-03T07:37:34+00:00"
         },
         {
             "name": "symfony/console",
-            "version": "v3.3.13",
+            "version": "v3.3.15",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/console.git",
-                "reference": "63cd7960a0a522c3537f6326706d7f3b8de65805"
+                "reference": "56f07f63c80baeb43ab259abc42c6cecf6461fae"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/console/zipball/63cd7960a0a522c3537f6326706d7f3b8de65805",
-                "reference": "63cd7960a0a522c3537f6326706d7f3b8de65805",
+                "url": "https://api.github.com/repos/symfony/console/zipball/56f07f63c80baeb43ab259abc42c6cecf6461fae",
+                "reference": "56f07f63c80baeb43ab259abc42c6cecf6461fae",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Symfony Console Component",
             "homepage": "https://symfony.com",
-            "time": "2017-11-16T15:24:32+00:00"
+            "time": "2018-01-03T07:37:11+00:00"
         },
         {
             "name": "symfony/css-selector",
-            "version": "v3.3.13",
+            "version": "v3.4.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/css-selector.git",
-                "reference": "66e6e046032ebdf1f562c26928549f613d428bd1"
+                "reference": "e66394bc7610e69279bfdb3ab11b4fe65403f556"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/css-selector/zipball/66e6e046032ebdf1f562c26928549f613d428bd1",
-                "reference": "66e6e046032ebdf1f562c26928549f613d428bd1",
+                "url": "https://api.github.com/repos/symfony/css-selector/zipball/e66394bc7610e69279bfdb3ab11b4fe65403f556",
+                "reference": "e66394bc7610e69279bfdb3ab11b4fe65403f556",
                 "shasum": ""
             },
             "require": {
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.3-dev"
+                    "dev-master": "3.4-dev"
                 }
             },
             "autoload": {
             ],
             "description": "Symfony CssSelector Component",
             "homepage": "https://symfony.com",
-            "time": "2017-11-05T15:47:03+00:00"
+            "time": "2018-01-03T07:37:34+00:00"
         },
         {
             "name": "symfony/debug",
-            "version": "v3.3.13",
+            "version": "v3.4.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/debug.git",
-                "reference": "74557880e2846b5c84029faa96b834da37e29810"
+                "reference": "603b95dda8b00020e4e6e60dc906e7b715b1c245"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/debug/zipball/74557880e2846b5c84029faa96b834da37e29810",
-                "reference": "74557880e2846b5c84029faa96b834da37e29810",
+                "url": "https://api.github.com/repos/symfony/debug/zipball/603b95dda8b00020e4e6e60dc906e7b715b1c245",
+                "reference": "603b95dda8b00020e4e6e60dc906e7b715b1c245",
                 "shasum": ""
             },
             "require": {
                 "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2"
             },
             "require-dev": {
-                "symfony/http-kernel": "~2.8|~3.0"
+                "symfony/http-kernel": "~2.8|~3.0|~4.0"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.3-dev"
+                    "dev-master": "3.4-dev"
                 }
             },
             "autoload": {
             ],
             "description": "Symfony Debug Component",
             "homepage": "https://symfony.com",
-            "time": "2017-11-10T16:38:39+00:00"
+            "time": "2018-01-03T17:14:19+00:00"
         },
         {
             "name": "symfony/dependency-injection",
-            "version": "v3.3.13",
+            "version": "v3.3.15",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/dependency-injection.git",
-                "reference": "4e84f5af2c2d51ee3dee72df40b7fc08f49b4ab8"
+                "reference": "e63cd8163d72a2a7722a40790ae80f2c0c8b50d1"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/4e84f5af2c2d51ee3dee72df40b7fc08f49b4ab8",
-                "reference": "4e84f5af2c2d51ee3dee72df40b7fc08f49b4ab8",
+                "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/e63cd8163d72a2a7722a40790ae80f2c0c8b50d1",
+                "reference": "e63cd8163d72a2a7722a40790ae80f2c0c8b50d1",
                 "shasum": ""
             },
             "require": {
                 "psr/container": "^1.0"
             },
             "conflict": {
-                "symfony/config": "<3.3.1",
+                "symfony/config": "<3.3.7",
                 "symfony/finder": "<3.3",
                 "symfony/yaml": "<3.3"
             },
             ],
             "description": "Symfony DependencyInjection Component",
             "homepage": "https://symfony.com",
-            "time": "2017-11-13T18:10:32+00:00"
+            "time": "2018-01-03T07:37:11+00:00"
         },
         {
             "name": "symfony/dom-crawler",
-            "version": "v3.3.13",
+            "version": "v3.4.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/dom-crawler.git",
-                "reference": "cebe3c068867956e012d9135282ba6a05d8a259e"
+                "reference": "09bd97b844b3151fab82f2fdd62db9c464b3910a"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/cebe3c068867956e012d9135282ba6a05d8a259e",
-                "reference": "cebe3c068867956e012d9135282ba6a05d8a259e",
+                "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/09bd97b844b3151fab82f2fdd62db9c464b3910a",
+                "reference": "09bd97b844b3151fab82f2fdd62db9c464b3910a",
                 "shasum": ""
             },
             "require": {
                 "symfony/polyfill-mbstring": "~1.0"
             },
             "require-dev": {
-                "symfony/css-selector": "~2.8|~3.0"
+                "symfony/css-selector": "~2.8|~3.0|~4.0"
             },
             "suggest": {
                 "symfony/css-selector": ""
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.3-dev"
+                    "dev-master": "3.4-dev"
                 }
             },
             "autoload": {
             ],
             "description": "Symfony DomCrawler Component",
             "homepage": "https://symfony.com",
-            "time": "2017-11-05T15:47:03+00:00"
+            "time": "2018-01-03T07:37:34+00:00"
         },
         {
             "name": "symfony/event-dispatcher",
-            "version": "v3.3.13",
+            "version": "v3.4.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/event-dispatcher.git",
-                "reference": "271d8c27c3ec5ecee6e2ac06016232e249d638d9"
+                "reference": "26b87b6bca8f8f797331a30b76fdae5342dc26ca"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/271d8c27c3ec5ecee6e2ac06016232e249d638d9",
-                "reference": "271d8c27c3ec5ecee6e2ac06016232e249d638d9",
+                "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/26b87b6bca8f8f797331a30b76fdae5342dc26ca",
+                "reference": "26b87b6bca8f8f797331a30b76fdae5342dc26ca",
                 "shasum": ""
             },
             "require": {
             },
             "require-dev": {
                 "psr/log": "~1.0",
-                "symfony/config": "~2.8|~3.0",
-                "symfony/dependency-injection": "~3.3",
-                "symfony/expression-language": "~2.8|~3.0",
-                "symfony/stopwatch": "~2.8|~3.0"
+                "symfony/config": "~2.8|~3.0|~4.0",
+                "symfony/dependency-injection": "~3.3|~4.0",
+                "symfony/expression-language": "~2.8|~3.0|~4.0",
+                "symfony/stopwatch": "~2.8|~3.0|~4.0"
             },
             "suggest": {
                 "symfony/dependency-injection": "",
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.3-dev"
+                    "dev-master": "3.4-dev"
                 }
             },
             "autoload": {
             ],
             "description": "Symfony EventDispatcher Component",
             "homepage": "https://symfony.com",
-            "time": "2017-11-05T15:47:03+00:00"
+            "time": "2018-01-03T07:37:34+00:00"
         },
         {
             "name": "symfony/filesystem",
-            "version": "v3.3.13",
+            "version": "v3.4.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/filesystem.git",
-                "reference": "77db266766b54db3ee982fe51868328b887ce15c"
+                "reference": "e078773ad6354af38169faf31c21df0f18ace03d"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/filesystem/zipball/77db266766b54db3ee982fe51868328b887ce15c",
-                "reference": "77db266766b54db3ee982fe51868328b887ce15c",
+                "url": "https://api.github.com/repos/symfony/filesystem/zipball/e078773ad6354af38169faf31c21df0f18ace03d",
+                "reference": "e078773ad6354af38169faf31c21df0f18ace03d",
                 "shasum": ""
             },
             "require": {
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.3-dev"
+                    "dev-master": "3.4-dev"
                 }
             },
             "autoload": {
             ],
             "description": "Symfony Filesystem Component",
             "homepage": "https://symfony.com",
-            "time": "2017-11-07T14:12:55+00:00"
+            "time": "2018-01-03T07:37:34+00:00"
         },
         {
             "name": "symfony/polyfill-mbstring",
         },
         {
             "name": "symfony/process",
-            "version": "v2.8.31",
+            "version": "v2.8.33",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/process.git",
-                "reference": "d25449e031f600807949aab7cadbf267712f4eee"
+                "reference": "ea3226daa3c6789efa39570bfc6e5d55f7561a0a"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/process/zipball/d25449e031f600807949aab7cadbf267712f4eee",
-                "reference": "d25449e031f600807949aab7cadbf267712f4eee",
+                "url": "https://api.github.com/repos/symfony/process/zipball/ea3226daa3c6789efa39570bfc6e5d55f7561a0a",
+                "reference": "ea3226daa3c6789efa39570bfc6e5d55f7561a0a",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Symfony Process Component",
             "homepage": "https://symfony.com",
-            "time": "2017-11-05T15:25:56+00:00"
+            "time": "2018-01-03T07:36:31+00:00"
         },
         {
             "name": "symfony/translation",
-            "version": "v3.3.13",
+            "version": "v3.3.15",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/translation.git",
-                "reference": "373e553477e55cd08f8b86b74db766c75b987fdb"
+                "reference": "96be707e96ba9ac04964d8f556d0fbb33329411c"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/translation/zipball/373e553477e55cd08f8b86b74db766c75b987fdb",
-                "reference": "373e553477e55cd08f8b86b74db766c75b987fdb",
+                "url": "https://api.github.com/repos/symfony/translation/zipball/96be707e96ba9ac04964d8f556d0fbb33329411c",
+                "reference": "96be707e96ba9ac04964d8f556d0fbb33329411c",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Symfony Translation Component",
             "homepage": "https://symfony.com",
-            "time": "2017-11-07T14:12:55+00:00"
+            "time": "2018-01-03T07:37:11+00:00"
         },
         {
             "name": "symfony/yaml",
-            "version": "v3.3.13",
+            "version": "v3.3.15",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/yaml.git",
-                "reference": "0938408c4faa518d95230deabb5f595bf0de31b9"
+                "reference": "7c80d81b5805589be151b85b0df785f0dc3269cf"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/yaml/zipball/0938408c4faa518d95230deabb5f595bf0de31b9",
-                "reference": "0938408c4faa518d95230deabb5f595bf0de31b9",
+                "url": "https://api.github.com/repos/symfony/yaml/zipball/7c80d81b5805589be151b85b0df785f0dc3269cf",
+                "reference": "7c80d81b5805589be151b85b0df785f0dc3269cf",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Symfony Yaml Component",
             "homepage": "https://symfony.com",
-            "time": "2017-11-10T18:26:04+00:00"
+            "time": "2018-01-03T07:37:11+00:00"
         },
         {
             "name": "theseer/tokenizer",
diff --git a/course/tests/behat/general_section.feature b/course/tests/behat/general_section.feature
new file mode 100644 (file)
index 0000000..4a5a259
--- /dev/null
@@ -0,0 +1,32 @@
+@format @format_topics
+Feature: General section does not show in navigation when empty
+  In order to keep my navigation links relevant
+  As a teacher
+  The general section links should not appear in the navigation when the section is empty
+
+  Background:
+    Given the following "users" exist:
+      | username | firstname | lastname | email            |
+      | teacher1 | Teacher   | 1        | teacher1@example.com |
+    And the following "courses" exist:
+      | fullname | shortname | format | coursedisplay | numsections |
+      | Course 1 | C1        | topics | 0             | 5           |
+    And the following "course enrolments" exist:
+      | user     | course | role           |
+      | teacher1 | C1     | editingteacher |
+    And I log in as "teacher1"
+    And I am on "Course 1" course homepage with editing mode on
+    And I add the "Navigation" block if not present
+    And I add a "Forum" to section "1" and I fill the form with:
+      | Forum name | Test forum name |
+      | Description | Test forum description |
+
+  Scenario: General section is visible in navigation when it is not empty
+    When I move "Test forum name" activity to section "0"
+    And I am on "Course 1" course homepage
+    Then I should see "General" in the "Navigation" "block"
+
+  Scenario: General section is not visible in navigation when it is empty
+    When I move "Test forum name" activity to section "3"
+    And I am on "Course 1" course homepage
+    Then I should not see "General" in the "Navigation" "block"
diff --git a/course/tests/behat/keyholder.feature b/course/tests/behat/keyholder.feature
new file mode 100644 (file)
index 0000000..41e4570
--- /dev/null
@@ -0,0 +1,62 @@
+@core @core_course @javascript
+Feature: Keyholder role is listed as course contact
+  As a student I need to know who the keyholder is to enrol in a course
+
+  Background:
+    Given I log in as "admin"
+    And I am on site homepage
+    And the following "categories" exist:
+      | name | category | idnumber |
+      | Cat 1 | 0 | CAT1 |
+    And I navigate to "Define roles" node in "Site administration > Users > Permissions"
+    And I click on "Add a new role" "button"
+    And I click on "Continue" "button"
+    And I set the following fields to these values:
+    | Short name | keyholder |
+    | Custom full name | Keyholder |
+    | contextlevel40 | 1 |
+    | contextlevel50 | 1 |
+    | enrol/self:holdkey | 1 |
+    And I click on "Create this role" "button"
+    And I navigate to "Courses" node in "Site administration > Appearance"
+    And I click on "Keyholder" "checkbox"
+    And I press "Save changes"
+    And the following "users" exist:
+      | username | firstname | lastname | email |
+      | teacher1 | Teacher | 1 | teacher1@example.com |
+      | keyholder1 | Keyholder | 1 | keyholder1@example.com |
+      | student1 | Student | 1 | teacher1@example.com |
+    And the following "courses" exist:
+      | fullname | shortname | format | coursedisplay | numsections | category |
+      | Course 1 | C1 | topics | 0 | 5 | CAT1 |
+    And I am on "Course 1" course homepage
+    And I add "Self enrolment" enrolment method with:
+      | Custom instance name | Test student enrolment |
+      | Enrolment key | letmein |
+    And I log out
+
+  Scenario: Keyholder assigned to a course
+    When I log in as "admin"
+    And the following "course enrolments" exist:
+      | user | course | role |
+      | teacher1 | C1 | editingteacher |
+      | keyholder1 | C1 | keyholder |
+    And I log out
+    And I log in as "student1"
+    And I am on site homepage
+    And I follow "Course 1"
+    Then I should see "Keyholder 1"
+
+  Scenario: Keyholder assigned to a category
+    When I log in as "admin"
+    And the following "role assigns" exist:
+      | user    | role          | contextlevel | reference |
+      | keyholder1 | keyholder       | Category     | CAT1      |
+    And the following "course enrolments" exist:
+      | user | course | role |
+      | teacher1 | C1 | editingteacher |
+    And I log out
+    And I log in as "student1"
+    And I am on site homepage
+    And I follow "Course 1"
+    Then I should see "Keyholder 1"
index 0447951..b31792f 100644 (file)
@@ -380,8 +380,9 @@ class course_enrolment_manager {
         $params = array('guestid' => $CFG->siteguest);
         if (!empty($search)) {
             $conditions = get_extra_user_fields($this->get_context());
-            $conditions[] = 'u.firstname';
-            $conditions[] = 'u.lastname';
+            foreach (get_all_user_name_fields() as $field) {
+                $conditions[] = 'u.'.$field;
+            }
             $conditions[] = $DB->sql_fullname('u.firstname', 'u.lastname');
             if ($searchanywhere) {
                 $searchparam = '%' . $search . '%';
diff --git a/install/lang/an/admin.php b/install/lang/an/admin.php
new file mode 100644 (file)
index 0000000..79c0a2d
--- /dev/null
@@ -0,0 +1,45 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Automatically generated strings for Moodle installer
+ *
+ * Do not edit this file manually! It contains just a subset of strings
+ * needed during the very first steps of installation. This file was
+ * generated automatically by export-installer.php (which is part of AMOS
+ * {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
+ * list of strings defined in /install/stringnames.txt.
+ *
+ * @package   installer
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$string['clianswerno'] = 'n';
+$string['cliansweryes'] = 's';
+$string['cliincorrectvalueerror'] = 'Error, valor incorrecta  "{$a->value}" pa "{$a->option}"';
+$string['cliincorrectvalueretry'] = 'Valor incorrecta, per favor, intente de nuevo';
+$string['clitypevalue'] = 'valor d\'o tipo';
+$string['clitypevaluedefault'] = 'valor d\'o tipo, prete Enter pa utilizar la valor per defecto ({$a})';
+$string['cliunknowoption'] = 'Opcions no reconoixidas:
+{$a}
+Per favor, utilice la opción Aduya.';
+$string['cliyesnoprompt'] = 'escriba s (pa sí) u n (pa no)';
+$string['environmentrequireinstall'] = 'ha d\'estar instalau/activau';
+$string['environmentrequireversion'] = 'versión {$a->needed} ye obligatoria y ye executando {$a->current}';
+$string['upgradekeyset'] = 'Clau d\'actualización (deixar en blanco pa no establir-la)';
diff --git a/install/lang/an/moodle.php b/install/lang/an/moodle.php
new file mode 100644 (file)
index 0000000..94f4624
--- /dev/null
@@ -0,0 +1,37 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Automatically generated strings for Moodle installer
+ *
+ * Do not edit this file manually! It contains just a subset of strings
+ * needed during the very first steps of installation. This file was
+ * generated automatically by export-installer.php (which is part of AMOS
+ * {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
+ * list of strings defined in /install/stringnames.txt.
+ *
+ * @package   installer
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$string['language'] = 'Idioma';
+$string['moodlelogo'] = 'Logo de Moodle';
+$string['next'] = 'Siguient';
+$string['previous'] = 'Anterior';
+$string['reload'] = 'Recargar';
index 7a48f3b..4a49dd7 100644 (file)
@@ -218,7 +218,7 @@ $string['configenablecourserequests'] = 'This will allow any user to request a c
 $string['configenablemobilewebservice'] = 'Enable mobile service for the official Moodle app or other app requesting it. For more information, read the {$a}';
 $string['configenablerssfeeds'] = 'If enabled, RSS feeds are generated by various features across the site, such as blogs, forums, database activities and glossaries. Note that RSS feeds also need to be enabled for the particular activity modules.';
 $string['configenablerssfeedsdisabled'] = 'It is not available because RSS feeds are disabled in all the Site. To enable them, go to the Variables settings under Admin Configuration.';
-$string['configenablerssfeedsdisabled2'] = 'RSS feeds are disabled at the server level. You need to enable them first in Server/RSS.';
+$string['configenablerssfeedsdisabled2'] = 'RSS feeds are disabled at the server level. You need to enable them first in Advanced Features / Enable RSS feeds.';
 $string['configenablesafebrowserintegration'] = 'This adds the choice \'Require Safe Exam Browser\' to the \'Browser security\' field on the quiz settings form. See http://www.safeexambrowser.org/ for more information.';
 $string['configenablestats'] = 'If you choose \'yes\' here, Moodle\'s cronjob will process the logs and gather some statistics.  Depending on the amount of traffic on your site, this can take awhile. If you enable this, you will be able to see some interesting graphs and statistics about each of your courses, or on a sitewide basis.';
 $string['configenabletrusttext'] = 'By default Moodle will always thoroughly clean text that comes from users to remove any possible bad scripts, media etc that could be a security risk.  The Trusted Content system is a way of giving particular users that you trust the ability to include these advanced features in their content without interference.  To enable this system, you need to first enable this setting, and then grant the Trusted Content permission to a specific Moodle role.  Texts created or uploaded by such users will be marked as trusted and will not be cleaned before display.';
index 50c59d1..ba4aebd 100644 (file)
@@ -35,7 +35,6 @@ $string['autotags'] = 'Add these tags';
 $string['autotags_help'] = 'Enter one or more local tags (separated by commas) that you want to automatically add to each blog entry copied from the external blog into your local blog.';
 $string['backupblogshelp'] = 'If enabled then blogs will be included in SITE automated backups';
 $string['blockexternalstitle'] = 'External blogs';
-$string['blocktitle'] = 'Blog tags block title';
 $string['blog'] = 'Blog';
 $string['blogaboutthis'] = 'Blog about this {$a->type}';
 $string['blogaboutthiscourse'] = 'Add an entry about this course';
index 80b20b6..207a522 100644 (file)
@@ -710,6 +710,7 @@ $string['emailresetconfirmsent'] = 'An email has been sent to your address at <b
 If you continue to have difficulty, contact the site administrator.';
 $string['emailtoprivatefiles'] = 'You can also e-mail files as attachments straight to your private files space. Simply attach your files to an e-mail and send it to {$a}';
 $string['emailtoprivatefilesdenied'] = 'Your administrator has disabled the option to upload your own private files.';
+$string['emailuserhasnone'] = 'There is no email address for the user.';
 $string['emailvia'] = '{$a->name} (via {$a->siteshortname})';
 $string['emptydragdropregion'] = 'empty region';
 $string['enable'] = 'Enable';
index b3c377d..e56d5d0 100644 (file)
@@ -41,6 +41,8 @@ $string['cannotdeletecate'] = 'You can\'t delete that category it is the default
 $string['cannotdeleteneededbehaviour'] = 'Cannot delete the question behaviour \'{$a}\'. There are other behaviours installed that rely on it.';
 $string['cannotdeleteqtypeinuse'] = 'You cannot delete the question type \'{$a}\'. There are questions of this type in the question bank.';
 $string['cannotdeleteqtypeneeded'] = 'You cannot delete the question type \'{$a}\'. There are other question types installed that rely on it.';
+$string['cannotdeletetopcat'] = 'Top categories can not be deleted.';
+$string['cannotedittopcat'] = 'Top categories can not be edited.';
 $string['cannotenable'] = 'Question type {$a} cannot be created directly.';
 $string['cannotenablebehaviour'] = 'Question behaviour {$a} cannot be used directly. It is for internal use only.';
 $string['cannotfindcate'] = 'Could not find category record';
@@ -269,6 +271,7 @@ $string['questionsinuse'] = '(* Questions marked by an asterisk are already in u
 $string['questionsmovedto'] = 'Questions still in use moved to "{$a}" in the parent course category.';
 $string['questionsrescuedfrom'] = 'Questions saved from context {$a}.';
 $string['questionsrescuedfrominfo'] = 'These questions (some of which may be hidden) were saved when context {$a} was deleted because they are still used by some quizzes or other activities.';
+$string['questiontags'] = 'Question tags';
 $string['questiontype'] = 'Question type';
 $string['questionuse'] = 'Use question in this activity';
 $string['questionvariant'] = 'Question variant';
@@ -397,6 +400,7 @@ $string['requiresgrading'] = 'Requires grading';
 $string['responsehistory'] = 'Response history';
 $string['restart'] = 'Start again';
 $string['restartwiththeseoptions'] = 'Start again with these options';
+$string['restoremultipletopcats'] = 'The backup file contains more than one top-level question categories for context {$a}.';
 $string['rightanswer'] = 'Right answer';
 $string['rightanswer_help'] = 'an automatically generated summary of the correct response. This can be limited, so you may wish to consider explaining the correct solution in the general feedback for the question, and turning this option off.';
 $string['saved'] = 'Saved: {$a}';
index 1a27a54..082274f 100644 (file)
Binary files a/lib/amd/build/chart_output_chartjs.min.js and b/lib/amd/build/chart_output_chartjs.min.js differ
index ef34547..4f0a0b0 100644 (file)
@@ -259,7 +259,7 @@ define([
                 fill: false,
                 backgroundColor: colors,
                 // Pie charts look better without borders.
-                borderColor: this._chart.getType() == Pie.prototype.TYPE ? null : colors,
+                borderColor: this._chart.getType() == Pie.prototype.TYPE ? '#fff' : colors,
                 lineTension: this._isSmooth(series) ? 0.3 : 0
             };
 
index 107335d..3202ea8 100644 (file)
@@ -101,6 +101,9 @@ class message {
     /** @var  string An email address which can be used to send an reply. */
     private $replyto;
 
+    /** @var  string A name which can be used with replyto. */
+    private $replytoname;
+
     /** @var  int Used internally to store the id of the row representing this message in DB. */
     private $savedmessageid;
 
@@ -130,6 +133,7 @@ class message {
         'contexturl',
         'contexturlname',
         'replyto',
+        'replytoname',
         'savedmessageid',
         'attachment',
         'attachname',
index d7e858a..d91d0c7 100644 (file)
@@ -372,6 +372,7 @@ class icon_system_fontawesome extends icon_system_font {
             'core:t/switch_minus' => 'fa-minus',
             'core:t/switch_plus' => 'fa-plus',
             'core:t/switch_whole' => 'fa-square-o',
+            'core:t/tags' => 'fa-tags',
             'core:t/unblock' => 'fa-commenting',
             'core:t/unlocked' => 'fa-unlock-alt',
             'core:t/unlock' => 'fa-lock',
index e8e4227..3a712b7 100644 (file)
@@ -1114,6 +1114,13 @@ $functions = array(
         'capabilities'  => 'moodle/question:flag',
         'services'      => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
     ),
+    'core_question_submit_tags_form' => array(
+        'classname'     => 'core_question_external',
+        'methodname'    => 'submit_tags_form',
+        'description'   => 'Update the question tags.',
+        'type'          => 'write',
+        'ajax' => true,
+    ),
     'core_rating_get_item_ratings' => array(
         'classname' => 'core_rating_external',
         'methodname' => 'get_item_ratings',
index 504fbfd..6b6dc90 100644 (file)
@@ -1935,5 +1935,43 @@ function xmldb_main_upgrade($oldversion) {
         upgrade_main_savepoint(true, 2017122200.01);
     }
 
+    if ($oldversion < 2018020500.00) {
+
+        $topcategory = new stdClass();
+        $topcategory->name = 'top'; // A non-real name for the top category. It will be localised at the display time.
+        $topcategory->info = '';
+        $topcategory->parent = 0;
+        $topcategory->sortorder = 0;
+
+        // Get the total record count - used for the progress bar.
+        $total = $DB->count_records_sql("SELECT COUNT(DISTINCT contextid) FROM {question_categories} WHERE parent = 0");
+
+        // Get the records themselves - a list of contextids.
+        $rs = $DB->get_recordset_sql("SELECT DISTINCT contextid FROM {question_categories} WHERE parent = 0");
+
+        // For each context, create a single top-level category.
+        $i = 0;
+        $pbar = new progress_bar('createtopquestioncategories', 500, true);
+        foreach ($rs as $contextid => $notused) {
+            $topcategory->contextid = $contextid;
+            $topcategory->stamp = make_unique_id_code();
+
+            $topcategoryid = $DB->insert_record('question_categories', $topcategory);
+
+            $DB->set_field_select('question_categories', 'parent', $topcategoryid,
+                    'contextid = ? AND id <> ? AND parent = 0',
+                    array($contextid, $topcategoryid));
+
+            // Update progress.
+            $i++;
+            $pbar->update($i, $total, "Creating top-level question categories - $i/$total.");
+        }
+
+        $rs->close();
+
+        // Main savepoint reached.
+        upgrade_main_savepoint(true, 2018020500.00);
+    }
+
     return true;
 }
index 0f34ef2..9528c10 100644 (file)
@@ -6534,3 +6534,45 @@ function allow_switch($fromroleid, $targetroleid) {
 
     core_role_set_switch_allowed($fromroleid, $targetroleid);
 }
+
+/**
+ * Organise categories into a single parent category (called the 'Top' category) per context.
+ *
+ * @param array $categories List of question categories in the format of ["$categoryid,$contextid" => $category].
+ * @param array $pcontexts List of context ids.
+ * @return array
+ * @deprecated since Moodle 3.5. MDL-61132
+ */
+function question_add_tops($categories, $pcontexts) {
+    debugging('question_add_tops() has been deprecated. You may want to pass $top = true to get_categories_for_contexts().',
+            DEBUG_DEVELOPER);
+
+    $topcats = array();
+    foreach ($pcontexts as $context) {
+        $topcat = question_get_top_category($context, true);
+
+        $newcat = new stdClass();
+        $newcat->id = "{$topcat->id},$context";
+        $newcat->name = get_string('top');
+        $newcat->parent = 0;
+        $newcat->contextid = $context;
+        $topcats["{$topcat->id},$context"] = $newcat;
+    }
+    // Put topcats in at beginning of array - they'll be sorted into different contexts later.
+    return array_merge($topcats, $categories);
+}
+
+/**
+ * Checks if the question category is the highest-level category in the context that can be edited, and has no siblings.
+ *
+ * @param int $categoryid a category id.
+ * @return bool
+ * @deprecated since Moodle 3.5. MDL-61132
+ */
+function question_is_only_toplevel_category_in_context($categoryid) {
+    debugging('question_is_only_toplevel_category_in_context() has been deprecated. '
+            . 'Please update your code to use question_is_only_child_of_top_category_in_context() instead.',
+            DEBUG_DEVELOPER);
+
+    return question_is_only_child_of_top_category_in_context($categoryid);
+}
index c1a4254..0f67664 100644 (file)
@@ -537,6 +537,18 @@ class stored_file {
      * @return bool success
      */
     public function archive_file(file_archive $filearch, $archivepath) {
+        if ($this->repository) {
+            $this->sync_external_file();
+            if ($this->compare_to_string('')) {
+                // This file is not stored locally - attempt to retrieve it from the repository.
+                // This may happen if the repository deliberately does not fetch files, or if there is a failure with the sync.
+                $fileinfo = $this->repository->get_file($this->get_reference());
+                if (isset($fileinfo['path'])) {
+                    return $filearch->add_file_from_pathname($archivepath, $fileinfo['path']);
+                }
+            }
+        }
+
         return $this->filesystem->add_storedfile_to_archive($this, $filearch, $archivepath);
     }
 
index 57021a7..904975f 100644 (file)
@@ -971,7 +971,6 @@ function add_indented_names($categories, $nochildrenof = -1) {
  */
 function question_category_select_menu($contexts, $top = false, $currentcat = 0,
         $selected = "", $nochildrenof = -1) {
-    global $OUTPUT;
     $categoriesarray = question_category_options($contexts, $top, $currentcat,
             false, $nochildrenof);
     if ($selected) {
@@ -994,8 +993,8 @@ function question_category_select_menu($contexts, $top = false, $currentcat = 0,
  */
 function question_get_default_category($contextid) {
     global $DB;
-    $category = $DB->get_records('question_categories',
-            array('contextid' => $contextid), 'id', '*', 0, 1);
+    $category = $DB->get_records_select('question_categories', 'contextid = ? AND parent <> 0',
+            array($contextid), 'id', '*', 0, 1);
     if (!empty($category)) {
         return reset($category);
     } else {
@@ -1003,6 +1002,51 @@ function question_get_default_category($contextid) {
     }
 }
 
+/**
+ * Gets the top category in the given context.
+ * This function can optionally create the top category if it doesn't exist.
+ *
+ * @param int $contextid A context id.
+ * @param bool $create Whether create a top category if it doesn't exist.
+ * @return bool|stdClass The top question category for that context, or false if none.
+ */
+function question_get_top_category($contextid, $create = false) {
+    global $DB;
+    $category = $DB->get_record('question_categories',
+            array('contextid' => $contextid, 'parent' => 0));
+
+    if (!$category && $create) {
+        // We need to make one.
+        $category = new stdClass();
+        $category->name = 'top'; // A non-real name for the top category. It will be localised at the display time.
+        $category->info = '';
+        $category->contextid = $contextid;
+        $category->parent = 0;
+        $category->sortorder = 0;
+        $category->stamp = make_unique_id_code();
+        $category->id = $DB->insert_record('question_categories', $category);
+    }
+
+    return $category;
+}
+
+/**
+ * Gets the list of top categories in the given contexts in the array("categoryid,categorycontextid") format.
+ *
+ * @param array $contextids List of context ids
+ * @return array
+ */
+function question_get_top_categories_for_contexts($contextids) {
+    global $DB;
+
+    $concatsql = $DB->sql_concat_join("','", ['id', 'contextid']);
+    list($insql, $params) = $DB->get_in_or_equal($contextids);
+    $sql = "SELECT $concatsql FROM {question_categories} WHERE contextid $insql AND parent = 0";
+    $topcategories = $DB->get_fieldset_sql($sql, $params);
+
+    return $topcategories;
+}
+
 /**
  * Gets the default category in the most specific context.
  * If no categories exist yet then default ones are created in all contexts.
@@ -1023,15 +1067,16 @@ function question_make_default_categories($contexts) {
     $preferredness = 0;
     // If it already exists, just return it.
     foreach ($contexts as $key => $context) {
+        $topcategory = question_get_top_category($context->id, true);
         if (!$exists = $DB->record_exists("question_categories",
-                array('contextid' => $context->id))) {
+                array('contextid' => $context->id, 'parent' => $topcategory->id))) {
             // Otherwise, we need to make one
             $category = new stdClass();
             $contextname = $context->get_context_name(false, true);
             $category->name = get_string('defaultfor', 'question', $contextname);
             $category->info = get_string('defaultinfofor', 'question', $contextname);
             $category->contextid = $context->id;
-            $category->parent = 0;
+            $category->parent = $topcategory->id;
             // By default, all categories get this number, and are sorted alphabetically.
             $category->sortorder = 999;
             $category->stamp = make_unique_id_code();
@@ -1061,20 +1106,29 @@ function question_make_default_categories($contexts) {
  *
  * @param mixed $contexts either a single contextid, or a comma-separated list of context ids.
  * @param string $sortorder used as the ORDER BY clause in the select statement.
+ * @param bool $top Whether to return the top categories or not.
  * @return array of category objects.
  */
-function get_categories_for_contexts($contexts, $sortorder = 'parent, sortorder, name ASC') {
+function get_categories_for_contexts($contexts, $sortorder = 'parent, sortorder, name ASC', $top = false) {
     global $DB;
+    $topwhere = $top ? '' : 'AND c.parent <> 0';
     return $DB->get_records_sql("
             SELECT c.*, (SELECT count(1) FROM {question} q
                         WHERE c.id = q.category AND q.hidden='0' AND q.parent='0') AS questioncount
               FROM {question_categories} c
-             WHERE c.contextid IN ($contexts)
+             WHERE c.contextid IN ($contexts) $topwhere
           ORDER BY $sortorder");
 }
 
 /**
  * Output an array of question categories.
+ *
+ * @param array $contexts The list of contexts.
+ * @param bool $top Whether to return the top categories or not.
+ * @param int $currentcat
+ * @param bool $popupform
+ * @param int $nochildrenof
+ * @return array
  */
 function question_category_options($contexts, $top = false, $currentcat = 0,
         $popupform = false, $nochildrenof = -1) {
@@ -1085,13 +1139,13 @@ function question_category_options($contexts, $top = false, $currentcat = 0,
     }
     $contextslist = join($pcontexts, ', ');
 
-    $categories = get_categories_for_contexts($contextslist);
-
-    $categories = question_add_context_in_key($categories);
+    $categories = get_categories_for_contexts($contextslist, 'parent, sortorder, name ASC', $top);
 
     if ($top) {
-        $categories = question_add_tops($categories, $pcontexts);
+        $categories = question_fix_top_names($categories);
     }
+
+    $categories = question_add_context_in_key($categories);
     $categories = add_indented_names($categories, $nochildrenof);
 
     // sort cats out into different contexts
@@ -1138,18 +1192,21 @@ function question_add_context_in_key($categories) {
     return $newcatarray;
 }
 
-function question_add_tops($categories, $pcontexts) {
-    $topcats = array();
-    foreach ($pcontexts as $context) {
-        $newcat = new stdClass();
-        $newcat->id = "0,$context";
-        $newcat->name = get_string('top');
-        $newcat->parent = -1;
-        $newcat->contextid = $context;
-        $topcats["0,$context"] = $newcat;
-    }
-    //put topcats in at beginning of array - they'll be sorted into different contexts later.
-    return array_merge($topcats, $categories);
+/**
+ * Finds top categories in the given categories hierarchy and replace their name with a proper localised string.
+ *
+ * @param array $categories An array of question categories.
+ * @return array The same question category list given to the function, with the top category names being translated.
+ */
+function question_fix_top_names($categories) {
+
+    foreach ($categories as $id => $category) {
+        if ($category->parent == 0) {
+            $categories[$id]->name = get_string('top');
+        }
+    }
+
+    return $categories;
 }
 
 /**
index d3395a7..42c4cad 100644 (file)
@@ -62,6 +62,7 @@ class core_message_testcase extends advanced_testcase {
         $message->contexturl = 'http://GalaxyFarFarAway.com';
         $message->contexturlname = 'Context name';
         $message->replyto = "random@example.com";
+        $message->replytoname = fullname($USER);
         $message->attachname = 'attachment';
         $content = array('*' => array('header' => ' test ', 'footer' => ' test ')); // Extra content for all types of messages.
         $message->set_additional_content('test', $content);
@@ -96,6 +97,7 @@ class core_message_testcase extends advanced_testcase {
         $this->assertSame($message->contexturl, $stdclass->contexturl);
         $this->assertSame($message->contexturlname, $stdclass->contexturlname);
         $this->assertSame($message->replyto, $stdclass->replyto);
+        $this->assertSame($message->replytoname, $stdclass->replytoname);
         $this->assertSame($message->attachname, $stdclass->attachname);
 
         // Extra content for fullmessage only.
index b699f72..da09a5b 100644 (file)
@@ -249,8 +249,8 @@ class core_questionlib_testcase extends advanced_testcase {
         $rc->execute_plan();
 
         // Get the created question category.
-        $restoredcategory = $DB->get_record('question_categories', array('contextid' => context_course::instance($course2->id)->id),
-            '*', MUST_EXIST);
+        $restoredcategory = $DB->get_record_select('question_categories', 'contextid = ? AND parent <> 0',
+                array(context_course::instance($course2->id)->id), '*', MUST_EXIST);
 
         // Check that there are two questions in the restored to course's context.
         $this->assertEquals(2, $DB->count_records('question', array('category' => $restoredcategory->id)));
@@ -334,6 +334,7 @@ class core_questionlib_testcase extends advanced_testcase {
         $this->assertEquals(0, $DB->count_records('question', $criteria));
 
         // Test that the feedback works.
+        $expected[] = array('top', get_string('unusedcategorydeleted', 'question'));
         $expected[] = array($qcat->name, get_string('unusedcategorydeleted', 'question'));
         $this->assertEquals($expected, $result);
     }
index 4d84daf..e3a9b58 100644 (file)
@@ -3140,13 +3140,14 @@ class assign {
      * Render the content in editor that is often used by plugin.
      *
      * @param string $filearea
-     * @param int  $submissionid
+     * @param int $submissionid
      * @param string $plugintype
      * @param string $editor
      * @param string $component
+     * @param bool $shortentext Whether to shorten the text content.
      * @return string
      */
-    public function render_editor_content($filearea, $submissionid, $plugintype, $editor, $component) {
+    public function render_editor_content($filearea, $submissionid, $plugintype, $editor, $component, $shortentext = false) {
         global $CFG;
 
         $result = '';
@@ -3154,6 +3155,9 @@ class assign {
         $plugin = $this->get_submission_plugin_by_type($plugintype);
 
         $text = $plugin->get_editor_text($editor, $submissionid);
+        if ($shortentext) {
+            $text = shorten_text($text, 140);
+        }
         $format = $plugin->get_editor_format($editor, $submissionid);
 
         $finaltext = file_rewrite_pluginfile_urls($text,
index 8afd96f..85ea4dc 100644 (file)
@@ -347,14 +347,18 @@ class assign_submission_onlinetext extends assign_submission_plugin {
         $showviewlink = true;
 
         if ($onlinetextsubmission) {
+            // This contains the shortened version of the text plus an optional 'Export to portfolio' button.
             $text = $this->assignment->render_editor_content(ASSIGNSUBMISSION_ONLINETEXT_FILEAREA,
                                                              $onlinetextsubmission->submission,
                                                              $this->get_type(),
                                                              'onlinetext',
-                                                             'assignsubmission_onlinetext');
+                                                             'assignsubmission_onlinetext', true);
 
+            // The actual submission text.
             $onlinetext = trim($onlinetextsubmission->onlinetext);
-            $shorttext = shorten_text($text, 140);
+            // The shortened version of the submission text.
+            $shorttext = shorten_text($onlinetext, 140);
+
             $plagiarismlinks = '';
 
             if (!empty($CFG->enableplagiarism)) {
@@ -366,12 +370,13 @@ class assign_submission_onlinetext extends assign_submission_plugin {
                     'course' => $this->assignment->get_course()->id,
                     'assignment' => $submission->assignment));
             }
-            if ($text != $shorttext) {
+            // We compare the actual text submission and the shortened version. If they are not equal, we show the word count.
+            if ($onlinetext != $shorttext) {
                 $wordcount = get_string('numwords', 'assignsubmission_onlinetext', count_words($onlinetext));
 
-                return $plagiarismlinks . $wordcount . $shorttext;
+                return $plagiarismlinks . $wordcount . $text;
             } else {
-                return $plagiarismlinks . $shorttext;
+                return $plagiarismlinks . $text;
             }
         }
         return '';
index 887c718..0ef6566 100644 (file)
@@ -29,6 +29,8 @@ defined('MOODLE_INTERNAL') || die;
 require_once($CFG->libdir . '/externallib.php');
 require_once($CFG->dirroot . '/mod/chat/lib.php');
 
+use mod_chat\external\chat_message_exporter;
+
 /**
  * Chat external functions
  *
@@ -612,4 +614,231 @@ class mod_chat_external extends external_api {
             )
         );
     }
+
+    /**
+     * Returns description of method parameters
+     *
+     * @return external_function_parameters
+     * @since Moodle 3.4
+     */
+    public static function get_sessions_parameters() {
+        return new external_function_parameters(
+            array(
+                'chatid' => new external_value(PARAM_INT, 'Chat instance id.'),
+                'groupid' => new external_value(PARAM_INT, 'Get messages from users in this group.
+                                                0 means that the function will determine the user group', VALUE_DEFAULT, 0),
+                'showall' => new external_value(PARAM_BOOL, 'Whether to show completed sessions or not.', VALUE_DEFAULT, false),
+            )
+        );
+    }
+
+    /**
+     * Retrieves chat sessions for a given chat.
+     *
+     * @param int $chatid the chat instance id
+     * @param int $groupid filter messages by this group. 0 to determine the group.
+     * @param bool $showall whether to include incomplete sessions or not
+     * @return array of warnings and the sessions
+     * @since Moodle 3.4
+     * @throws moodle_exception
+     */
+    public static function get_sessions($chatid, $groupid = 0, $showall = false) {
+        global $DB;
+
+        $params = self::validate_parameters(self::get_sessions_parameters(),
+                                            array(
+                                                'chatid' => $chatid,
+                                                'groupid' => $groupid,
+                                                'showall' => $showall,
+                                            ));
+        $sessions = $warnings = array();
+
+        // Request and permission validation.
+        $chat = $DB->get_record('chat', array('id' => $params['chatid']), '*', MUST_EXIST);
+        list($course, $cm) = get_course_and_cm_from_instance($chat, 'chat');
+
+        $context = context_module::instance($cm->id);
+        self::validate_context($context);
+
+        if (empty($chat->studentlogs) && !has_capability('mod/chat:readlog', $context)) {
+            throw new moodle_exception('nopermissiontoseethechatlog', 'chat');
+        }
+
+        if (!empty($params['groupid'])) {
+            $groupid = $params['groupid'];
+            // Determine is the group is visible to user.
+            if (!groups_group_visible($groupid, $course, $cm)) {
+                throw new moodle_exception('notingroup');
+            }
+        } else {
+            // Check to see if groups are being used here.
+            if ($groupmode = groups_get_activity_groupmode($cm)) {
+                $groupid = groups_get_activity_group($cm);
+                // Determine is the group is visible to user (this is particullary for the group 0).
+                if (!groups_group_visible($groupid, $course, $cm)) {
+                    throw new moodle_exception('notingroup');
+                }
+            } else {
+                $groupid = 0;
+            }
+        }
+
+        $messages = chat_get_session_messages($chat->id, $groupid, 0, 0, 'timestamp DESC');
+        if ($messages) {
+            $chatsessions = chat_get_sessions($messages, $params['showall']);
+            // Format sessions for external.
+            foreach ($chatsessions as $session) {
+                $sessionusers = array();
+                foreach ($session->sessionusers as $sessionuser => $usermessagecount) {
+                    $sessionusers[] = array(
+                        'userid' => $sessionuser,
+                        'messagecount' => $usermessagecount
+                    );
+                }
+                $session->sessionusers = $sessionusers;
+                $sessions[] = $session;
+            }
+        }
+
+        $result = array();
+        $result['sessions'] = $sessions;
+        $result['warnings'] = $warnings;
+        return $result;
+    }
+
+    /**
+     * Returns description of method result value
+     *
+     * @return external_description
+     * @since Moodle 3.4
+     */
+    public static function get_sessions_returns() {
+        return new external_single_structure(
+            array(
+                'sessions' => new external_multiple_structure(
+                    new external_single_structure(
+                        array(
+                            'sessionstart' => new external_value(PARAM_INT, 'Session start time.'),
+                            'sessionend' => new external_value(PARAM_INT, 'Session end time.'),
+                            'sessionusers' => new external_multiple_structure(
+                                new external_single_structure(
+                                    array(
+                                        'userid' => new external_value(PARAM_INT, 'User id.'),
+                                        'messagecount' => new external_value(PARAM_INT, 'Number of messages in the session.'),
+                                    )
+                                ), 'Session users.'
+                            ),
+                            'iscomplete' => new external_value(PARAM_BOOL, 'Whether the session is completed or not.'),
+                        )
+                    ),
+                    'list of users'
+                ),
+                'warnings' => new external_warnings()
+            )
+        );
+    }
+
+    /**
+     * Returns description of method parameters
+     *
+     * @return external_function_parameters
+     * @since Moodle 3.4
+     */
+    public static function get_session_messages_parameters() {
+        return new external_function_parameters(
+            array(
+                'chatid' => new external_value(PARAM_INT, 'Chat instance id.'),
+                'sessionstart' => new external_value(PARAM_INT, 'The session start time (timestamp).'),
+                'sessionend' => new external_value(PARAM_INT, 'The session end time (timestamp).'),
+                'groupid' => new external_value(PARAM_INT, 'Get messages from users in this group.
+                                                0 means that the function will determine the user group', VALUE_DEFAULT, 0),
+            )
+        );
+    }
+
+    /**
+     * Retrieves messages of the given chat session.
+     *
+     * @param int $chatid the chat instance id
+     * @param int $sessionstart the session start time (timestamp)
+     * @param int $sessionend the session end time (timestamp)
+     * @param int $groupid filter messages by this group. 0 to determine the group.
+     * @return array of warnings and the messages
+     * @since Moodle 3.4
+     * @throws moodle_exception
+     */
+    public static function get_session_messages($chatid, $sessionstart, $sessionend, $groupid = 0) {
+        global $DB, $PAGE;
+
+        $params = self::validate_parameters(self::get_session_messages_parameters(),
+                                            array(
+                                                'chatid' => $chatid,
+                                                'sessionstart' => $sessionstart,
+                                                'sessionend' => $sessionend,
+                                                'groupid' => $groupid,
+                                            ));
+        $messages = $warnings = array();
+
+        // Request and permission validation.
+        $chat = $DB->get_record('chat', array('id' => $params['chatid']), '*', MUST_EXIST);
+        list($course, $cm) = get_course_and_cm_from_instance($chat, 'chat');
+
+        $context = context_module::instance($cm->id);
+        self::validate_context($context);
+
+        if (empty($chat->studentlogs) && !has_capability('mod/chat:readlog', $context)) {
+            throw new moodle_exception('nopermissiontoseethechatlog', 'chat');
+        }
+
+        if (!empty($params['groupid'])) {
+            $groupid = $params['groupid'];
+            // Determine is the group is visible to user.
+            if (!groups_group_visible($groupid, $course, $cm)) {
+                throw new moodle_exception('notingroup');
+            }
+        } else {
+            // Check to see if groups are being used here.
+            if ($groupmode = groups_get_activity_groupmode($cm)) {
+                $groupid = groups_get_activity_group($cm);
+                // Determine is the group is visible to user (this is particullary for the group 0).
+                if (!groups_group_visible($groupid, $course, $cm)) {
+                    throw new moodle_exception('notingroup');
+                }
+            } else {
+                $groupid = 0;
+            }
+        }
+
+        $messages = chat_get_session_messages($chat->id, $groupid, $params['sessionstart'], $params['sessionend'],
+            'timestamp ASC');
+        if ($messages) {
+            foreach ($messages as $message) {
+                $exporter = new chat_message_exporter($message, array('context' => $context));
+                $returneditems[] = $exporter->export($PAGE->get_renderer('core'));
+            }
+        }
+
+        $result = array(
+            'messages' => $messages,
+            'warnings' => $warnings,
+        );
+        return $result;
+    }
+
+    /**
+     * Returns description of method result value
+     *
+     * @return external_description
+     * @since Moodle 3.4
+     */
+    public static function get_session_messages_returns() {
+        return new external_single_structure(
+            array(
+                'messages' => new external_multiple_structure(
+                    chat_message_exporter::get_read_structure()
+                ),
+                'warnings' => new external_warnings()
+            )
+        );
+    }
 }
diff --git a/mod/chat/classes/external/chat_message_exporter.php b/mod/chat/classes/external/chat_message_exporter.php
new file mode 100644 (file)
index 0000000..1f3cac4
--- /dev/null
@@ -0,0 +1,101 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Class for exporting a chat message.
+ *
+ * @package    mod_chat
+ * @copyright  2017 Juan Leyva <juan@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace mod_chat\external;
+defined('MOODLE_INTERNAL') || die();
+
+use core\external\exporter;
+
+/**
+ * Class for exporting a chat message.
+ *
+ * @copyright  2017 Juan Leyva <juan@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class chat_message_exporter extends exporter {
+
+    /**
+     * Defines exporter properties.
+     *
+     * @return array
+     */
+    protected static function define_properties() {
+        return array(
+            'id' => array(
+                'type' => PARAM_INT,
+                'description' => 'The message record id.',
+            ),
+            'chatid' => array(
+                'type' => PARAM_INT,
+                'description' => 'The chat id.',
+                'default' => 0,
+            ),
+            'userid' => array(
+                'type' => PARAM_INT,
+                'description' => 'The user who wrote the message.',
+                'default' => 0,
+            ),
+            'groupid' => array(
+                'type' => PARAM_INT,
+                'description' => 'The group this message belongs to.',
+                'default' => 0,
+            ),
+            'issystem' => array(
+                'type' => PARAM_BOOL,
+                'description' => 'Whether is a system message or not.',
+                'default' => false,
+            ),
+            'message' => array(
+                'type' => PARAM_RAW,
+                'description' => 'The message text.',
+            ),
+            'timestamp' => array(
+                'type' => PARAM_INT,
+                'description' => 'The message timestamp (indicates when the message was sent).',
+                'default' => 0,
+            ),
+        );
+    }
+
+    /**
+     * Defines related information.
+     *
+     * @return array
+     */
+    protected static function define_related() {
+        return array(
+            'context' => 'context',
+        );
+    }
+
+    /**
+     * Get the formatting parameters for the name.
+     *
+     * @return array
+     */
+    protected function get_format_parameters_for_message() {
+        return [
+            'component' => 'mod_chat',
+        ];
+    }
+}
index 2e0f946..6bbd928 100644 (file)
@@ -81,5 +81,19 @@ $functions = array(
         'type'          => 'read',
         'capabilities'  => '',
         'services'      => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
-    )
+    ),
+    'mod_chat_get_sessions' => array(
+        'classname'     => 'mod_chat_external',
+        'methodname'    => 'get_sessions',
+        'description'   => 'Retrieves chat sessions for a given chat.',
+        'type'          => 'read',
+        'services'      => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
+    ),
+    'mod_chat_get_session_messages' => array(
+        'classname'     => 'mod_chat_external',
+        'methodname'    => 'get_session_messages',
+        'description'   => 'Retrieves messages of the given chat session.',
+        'type'          => 'read',
+        'services'      => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
+    ),
 );
index 9bec703..7de8f07 100644 (file)
@@ -1480,3 +1480,98 @@ function mod_chat_core_calendar_provide_event_action(calendar_event $event,
         );
     }
 }
+
+/**
+ * Given a set of messages for a chat, return the completed chat sessions (including optionally not completed ones).
+ *
+ * @param  array $messages list of messages from a chat
+ * @param  bool $showall   whether to include incomplete sessions or not
+ * @return array           the list of sessions
+ * @since  Moodle 3.4
+ */
+function chat_get_sessions($messages, $showall = false) {
+    $sessions     = array();
+    $sessiongap   = 5 * 60;    // 5 minutes silence means a new session.
+    $sessionend   = 0;
+    $sessionstart = 0;
+    $sessionusers = array();
+    $lasttime     = 0;
+
+    $messagesleft = count($messages);
+
+    foreach ($messages as $message) {  // We are walking BACKWARDS through the messages.
+
+        $messagesleft --;              // Countdown.
+
+        if (!$lasttime) {
+            $lasttime = $message->timestamp;
+        }
+        if (!$sessionend) {
+            $sessionend = $message->timestamp;
+        }
+        if ((($lasttime - $message->timestamp) < $sessiongap) and $messagesleft) {  // Same session.
+            if ($message->userid and !$message->issystem) {       // Remember user and count messages.
+                if (empty($sessionusers[$message->userid])) {
+                    $sessionusers[$message->userid] = 1;
+                } else {
+                    $sessionusers[$message->userid] ++;
+                }
+            }
+        } else {
+            $sessionstart = $lasttime;
+
+            $iscomplete = ($sessionend - $sessionstart > 60 and count($sessionusers) > 1);
+            if ($showall or $iscomplete) {
+                $sessions[] = (object) array(
+                    'sessionstart' => $sessionstart,
+                    'sessionend' => $sessionend,
+                    'sessionusers' => $sessionusers,
+                    'iscomplete' => $iscomplete,
+                );
+            }
+
+            $sessionend = $message->timestamp;
+            $sessionusers = array();
+            $sessionusers[$message->userid] = 1;
+        }
+        $lasttime = $message->timestamp;
+    }
+    return $sessions;
+}
+
+/**
+ * Return the messages of the given chat session.
+ *
+ * @param  int $chatid      the chat id
+ * @param  mixed $group     false if groups not used, int if groups used, 0 means all groups
+ * @param  int $start       the session start timestamp (0 to not filter by time)
+ * @param  int $end         the session end timestamp (0 to not filter by time)
+ * @param  string $sort     an order to sort the results in (optional, a valid SQL ORDER BY parameter)
+ * @return array session messages
+ * @since  Moodle 3.4
+ */
+function chat_get_session_messages($chatid, $group = false, $start = 0, $end = 0, $sort = '') {
+    global $DB;
+
+    $params = array('chatid' => $chatid);
+
+    // If the user is allocated to a group, only show messages from people in the same group, or no group.
+    if ($group) {
+        $groupselect = " AND (groupid = :currentgroup OR groupid = 0)";
+        $params['currentgroup'] = $group;
+    } else {
+        $groupselect = "";
+    }
+
+    $select = "chatid = :chatid $groupselect";
+    if (!empty($start)) {
+        $select .= ' AND timestamp >= :start';
+        $params['start'] = $start;
+    }
+    if (!empty($end)) {
+        $select .= ' AND timestamp <= :end';
+        $params['end'] = $end;
+    }
+
+    return $DB->get_records_select('chat_messages', $select, $params, $sort);
+}
index 8e1fd14..d41a121 100644 (file)
@@ -96,29 +96,14 @@ if ($start and $end and !$confirmdelete) {   // Show a full transcript.
     $currentgroup = groups_get_activity_group($cm, true);
     groups_print_activity_menu($cm, $CFG->wwwroot . "/mod/chat/report.php?id=$cm->id");
 
-    $params = array('currentgroup' => $currentgroup, 'chatid' => $chat->id, 'start' => $start, 'end' => $end);
-
-    // If the user is allocated to a group, only show messages from people
-    // in the same group, or no group.
-    if ($currentgroup) {
-        $groupselect = " AND (groupid = :currentgroup OR groupid = 0)";
-    } else {
-        $groupselect = "";
-    }
-
     if ($deletesession and has_capability('mod/chat:deletelog', $context)) {
         echo $OUTPUT->confirm(get_string('deletesessionsure', 'chat'),
                      "report.php?id=$cm->id&deletesession=1&confirmdelete=1&start=$start&end=$end",
                      "report.php?id=$cm->id");
     }
 
-    if (!$messages = $DB->get_records_select('chat_messages',
-                                             "chatid = :chatid AND timestamp >= :start AND timestamp <= :end $groupselect",
-                                             $params,
-                                             "timestamp ASC")) {
-
+    if (!$messages = chat_get_session_messages($chat->id, $currentgroup, $start, $end, 'timestamp ASC')) {
         echo $OUTPUT->heading(get_string('nomessages', 'chat'));
-
     } else {
         echo '<p class="boxaligncenter">'.userdate($start).' --> '. userdate($end).'</p>';
 
@@ -198,7 +183,7 @@ if ($deletesession and has_capability('mod/chat:deletelog', $context)
 
 // Get the messages.
 if (empty($messages)) {   // May have already got them above.
-    if (!$messages = $DB->get_records_select('chat_messages', "chatid = :chatid $groupselect", $params, "timestamp DESC")) {
+    if (!$messages = chat_get_session_messages($chat->id, $currentgroup, 0, 0, 'timestamp DESC')) {
         echo $OUTPUT->heading(get_string('nomessages', 'chat'), 3);
         echo $OUTPUT->footer();
         exit;
@@ -212,92 +197,58 @@ if ($showall) {
 }
 
 // Show all the sessions.
-
-$sessiongap        = 5 * 60;    // 5 minutes silence means a new session.
-$sessionend        = 0;
-$sessionstart      = 0;
-$sessionusers      = array();
-$lasttime          = 0;
 $completesessions  = 0;
 
-$messagesleft = count($messages);
-
 echo '<div class="list-group">';
 
-foreach ($messages as $message) {  // We are walking BACKWARDS through the messages.
+$sessions = chat_get_sessions($messages, $showall);
 
-    $messagesleft --;              // Countdown.
+foreach ($sessions as $session) {
+    echo '<div class="list-group-item">';
+    echo '<p>'.userdate($session->sessionstart).' --> '. userdate($session->sessionend).'</p>';
 
-    if (!$lasttime) {
-        $lasttime = $message->timestamp;
-    }
-    if (!$sessionend) {
-        $sessionend = $message->timestamp;
-    }
-    if ((($lasttime - $message->timestamp) < $sessiongap) and $messagesleft) {  // Same session.
-        if ($message->userid and !$message->issystem) {     // Remember user and count messages.
-            if (empty($sessionusers[$message->userid])) {
-                $sessionusers[$message->userid] = 1;
-            } else {
-                $sessionusers[$message->userid] ++;
-            }
-        }
-    } else {
-        $sessionstart = $lasttime;
+    echo $OUTPUT->box_start();
 
-        $iscomplete = ($sessionend - $sessionstart > 60 and count($sessionusers) > 1);
-        if ($showall or $iscomplete) {
-
-            echo '<div class="list-group-item">';
-            echo '<p>'.userdate($sessionstart).' --> '. userdate($sessionend).'</p>';
-
-            echo $OUTPUT->box_start();
-
-            arsort($sessionusers);
-            foreach ($sessionusers as $sessionuser => $usermessagecount) {
-                if ($user = $DB->get_record('user', array('id' => $sessionuser))) {
-                    $OUTPUT->user_picture($user, array('courseid' => $course->id));
-                    echo '&nbsp;'.fullname($user, true); // XXX TODO  use capability instead of true.
-                    echo "&nbsp;($usermessagecount)<br />";
-                }
-            }
-
-            echo '<p align="right">';
-            echo "<a href=\"report.php?id=$cm->id&amp;start=$sessionstart&amp;end=$sessionend\">$strseesession</a>";
-            $participatedcap = (array_key_exists($USER->id, $sessionusers)
-                               && has_capability('mod/chat:exportparticipatedsession', $context));
-            if (!empty($CFG->enableportfolios) && ($canexportsess || $participatedcap)) {
-                require_once($CFG->libdir . '/portfoliolib.php');
-                $buttonoptions  = array(
-                    'id'    => $cm->id,
-                    'start' => $sessionstart,
-                    'end'   => $sessionend,
-                );
-                $button = new portfolio_add_button();
-                $button->set_callback_options('chat_portfolio_caller', $buttonoptions, 'mod_chat');
-                $portfoliobutton = $button->to_html(PORTFOLIO_ADD_TEXT_LINK);
-                if (!empty($portfoliobutton)) {
-                    echo '<br />' . $portfoliobutton;
-                }
-            }
-            if (has_capability('mod/chat:deletelog', $context)) {
-                $deleteurl = "report.php?id=$cm->id&amp;start=$sessionstart&amp;end=$sessionend&amp;deletesession=1";
-                echo "<br /><a href=\"$deleteurl\">$strdeletesession</a>";
-            }
-            echo '</p>';
-            echo $OUTPUT->box_end();
-            echo '</div>';
+    arsort($session->sessionusers);
+    foreach ($session->sessionusers as $sessionuser => $usermessagecount) {
+        if ($user = $DB->get_record('user', array('id' => $sessionuser))) {
+            $OUTPUT->user_picture($user, array('courseid' => $course->id));
+            echo '&nbsp;'.fullname($user, true); // XXX TODO  use capability instead of true.
+            echo "&nbsp;($usermessagecount)<br />";
         }
-        if ($iscomplete) {
-            $completesessions++;
+    }
+
+    echo '<p align="right">';
+    echo "<a href=\"report.php?id=$cm->id&amp;start=$session->sessionstart&amp;end=$session->sessionend\">$strseesession</a>";
+    $participatedcap = (array_key_exists($USER->id, $session->sessionusers)
+                       && has_capability('mod/chat:exportparticipatedsession', $context));
+    if (!empty($CFG->enableportfolios) && ($canexportsess || $participatedcap)) {
+        require_once($CFG->libdir . '/portfoliolib.php');
+        $buttonoptions  = array(
+            'id'    => $cm->id,
+            'start' => $session->sessionstart,
+            'end'   => $session->sessionend,
+        );
+        $button = new portfolio_add_button();
+        $button->set_callback_options('chat_portfolio_caller', $buttonoptions, 'mod_chat');
+        $portfoliobutton = $button->to_html(PORTFOLIO_ADD_TEXT_LINK);
+        if (!empty($portfoliobutton)) {
+            echo '<br />' . $portfoliobutton;
         }
+    }
+    if (has_capability('mod/chat:deletelog', $context)) {
+        $deleteurl = "report.php?id=$cm->id&amp;start=$session->sessionstart&amp;end=$session->sessionend&amp;deletesession=1";
+        echo "<br /><a href=\"$deleteurl\">$strdeletesession</a>";
+    }
+    echo '</p>';
+    echo $OUTPUT->box_end();
+    echo '</div>';
 
-        $sessionend = $message->timestamp;
-        $sessionusers = array();
-        $sessionusers[$message->userid] = 1;
+    if ($session->iscomplete) {
+        $completesessions++;
     }
-    $lasttime = $message->timestamp;
 }
+
 echo '</div>';
 
 if (!empty($CFG->enableportfolios) && $canexportsess) {
index f17fb2e..2443f71 100644 (file)
@@ -290,4 +290,206 @@ class mod_chat_external_testcase extends externallib_advanced_testcase {
         $this->assertCount(2, $chats['chats']);
 
     }
+
+    /**
+     * Test get_sessions_empty_chat
+     */
+    public function test_get_sessions_empty_chat() {
+        global $DB;
+
+        $this->resetAfterTest(true);
+
+        // Setup test data.
+        $this->setAdminUser();
+        $course = $this->getDataGenerator()->create_course();
+        $chat = $this->getDataGenerator()->create_module('chat', array('course' => $course->id));
+
+        $result = mod_chat_external::get_sessions($chat->id);
+        $result = external_api::clean_returnvalue(mod_chat_external::get_sessions_returns(), $result);
+        $this->assertEmpty($result['sessions']);
+        $this->assertEmpty($result['warnings']);
+    }
+
+
+    /**
+     * Test get_sessions_no_permissions_for_student
+     */
+    public function test_get_sessions_no_permissions_for_student() {
+        global $DB;
+
+        $this->resetAfterTest(true);
+
+        // Setup test data.
+        $this->setAdminUser();
+        $course = $this->getDataGenerator()->create_course();
+        // Disable logs for students.
+        $chat = $this->getDataGenerator()->create_module('chat', array('course' => $course->id, 'studentlogs' => 0));
+        // The admin has permissions to check logs.
+        $result = mod_chat_external::get_sessions($chat->id);
+        $result = external_api::clean_returnvalue(mod_chat_external::get_sessions_returns(), $result);
+        $this->assertEmpty($result['sessions']);
+        $this->assertEmpty($result['warnings']);
+
+        $user = self::getDataGenerator()->create_user();
+        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
+        unassign_capability('mod/chat:readlog', $studentrole->id);
+        accesslib_clear_all_caches_for_unit_testing();
+
+        $this->getDataGenerator()->enrol_user($user->id, $course->id, $studentrole->id);
+        $this->setUser($user);
+        // Students don't have permissions.
+        $this->expectException('moodle_exception');
+        mod_chat_external::get_sessions($chat->id);
+    }
+
+    /**
+     * Test get_sessions_not_completed_session
+     */
+    public function test_get_sessions_not_completed_session() {
+        global $DB;
+
+        $this->resetAfterTest(true);
+
+        // Setup test data.
+        $this->setAdminUser();
+        $course = $this->getDataGenerator()->create_course();
+        $chat = $this->getDataGenerator()->create_module('chat', array('course' => $course->id));
+
+        $user = self::getDataGenerator()->create_user();
+        $this->setUser($user);
+        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
+        $this->getDataGenerator()->enrol_user($user->id, $course->id, $studentrole->id);
+
+        // Start a chat and send just one message.
+        $result = mod_chat_external::login_user($chat->id);
+        $result = external_api::clean_returnvalue(mod_chat_external::login_user_returns(), $result);
+        $chatsid = $result['chatsid'];
+        $result = mod_chat_external::send_chat_message($chatsid, 'hello!');
+        $result = external_api::clean_returnvalue(mod_chat_external::send_chat_message_returns(), $result);
+
+        // Check session is not marked as completed so it is not returned.
+        $result = mod_chat_external::get_sessions($chat->id);
+        $result = external_api::clean_returnvalue(mod_chat_external::get_sessions_returns(), $result);
+        $this->assertEmpty($result['sessions']);
+        $this->assertEmpty($result['warnings']);
+
+        // Pass showall parameter to indicate that we want not completed sessions.
+        $result = mod_chat_external::get_sessions($chat->id, 0, true);
+        $result = external_api::clean_returnvalue(mod_chat_external::get_sessions_returns(), $result);
+        $this->assertCount(1, $result['sessions']); // One session.
+        $this->assertFalse($result['sessions'][0]['iscomplete']); // Session not complete.
+        $this->assertEmpty($result['warnings']);
+    }
+
+    /**
+     * Test get_sessions_completed_session
+     */
+    public function test_get_sessions_completed_session() {
+        global $DB;
+
+        $this->resetAfterTest(true);
+
+        // Setup test data.
+        $this->setAdminUser();
+        $course = $this->getDataGenerator()->create_course();
+        $chat = $this->getDataGenerator()->create_module('chat', array('course' => $course->id));
+
+        $user1 = self::getDataGenerator()->create_user();
+        $user2 = self::getDataGenerator()->create_user();
+        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
+        $this->getDataGenerator()->enrol_user($user1->id, $course->id, $studentrole->id);
+        $this->getDataGenerator()->enrol_user($user2->id, $course->id, $studentrole->id);
+
+        // Start a chat and completeit.
+        $this->setUser($user1);
+        $result = mod_chat_external::login_user($chat->id);
+        $result = external_api::clean_returnvalue(mod_chat_external::login_user_returns(), $result);
+        $chatsid = $result['chatsid'];
+        $result = mod_chat_external::send_chat_message($chatsid, 'hello!');
+        $result = external_api::clean_returnvalue(mod_chat_external::send_chat_message_returns(), $result);
+        $this->setUser($user2);
+        $result = mod_chat_external::login_user($chat->id);
+        $result = external_api::clean_returnvalue(mod_chat_external::login_user_returns(), $result);
+        $chatsid = $result['chatsid'];
+        $result = mod_chat_external::send_chat_message($chatsid, 'hello to you!');
+        $result = external_api::clean_returnvalue(mod_chat_external::send_chat_message_returns(), $result);
+        // Need to change first messages and last message times to mark the session completed.
+        // We receive 4 messages (2 system messages that indicates user joined and the 2 messages sent by the users).
+        $messages = $DB->get_records('chat_messages', array('chatid' => $chat->id));
+        // Messages just one hour ago and 70 seconds between them.
+        $timegap = 0;
+        $timenow = time();
+        foreach ($messages as $message) {
+            $DB->set_field('chat_messages', 'timestamp', $timenow - HOURSECS + $timegap, array('id' => $message->id));
+            $timegap += 70;
+        }
+        // Check session is completed.
+        $result = mod_chat_external::get_sessions($chat->id);
+        $result = external_api::clean_returnvalue(mod_chat_external::get_sessions_returns(), $result);
+        $this->assertCount(1, $result['sessions']); // One session.
+        $this->assertTrue($result['sessions'][0]['iscomplete']); // Session complete.
+        $this->assertEquals($timenow - HOURSECS + 70, $result['sessions'][0]['sessionstart']);  // First not system message time.
+        $this->assertEmpty($result['warnings']);
+    }
+
+    /**
+     * Test get_session_messages
+     */
+    public function test_get_session_messages() {
+        global $DB;
+
+        $this->resetAfterTest(true);
+
+        // Setup test data.
+        $this->setAdminUser();
+        $course = $this->getDataGenerator()->create_course();
+        $chat = $this->getDataGenerator()->create_module('chat', array('course' => $course->id));
+
+        $user1 = self::getDataGenerator()->create_user();
+        $user2 = self::getDataGenerator()->create_user();
+        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
+        $this->getDataGenerator()->enrol_user($user1->id, $course->id, $studentrole->id);
+        $this->getDataGenerator()->enrol_user($user2->id, $course->id, $studentrole->id);
+
+        // Start a chat and send a few messages.
+        $this->setUser($user1);
+        $result = mod_chat_external::login_user($chat->id);
+        $result = external_api::clean_returnvalue(mod_chat_external::login_user_returns(), $result);
+        $chatsid = $result['chatsid'];
+        mod_chat_external::send_chat_message($chatsid, 'hello!');
+        mod_chat_external::send_chat_message($chatsid, 'bye bye!');
+
+        $this->setUser($user2);
+        $result = mod_chat_external::login_user($chat->id);
+        $result = external_api::clean_returnvalue(mod_chat_external::login_user_returns(), $result);
+        $chatsid = $result['chatsid'];
+        mod_chat_external::send_chat_message($chatsid, 'greetings!');
+
+        // Pass showall parameter to indicate that we want not completed sessions.
+        $result = mod_chat_external::get_sessions($chat->id, 0, true);
+        $result = external_api::clean_returnvalue(mod_chat_external::get_sessions_returns(), $result);
+        $this->assertCount(1, $result['sessions']); // One session.
+
+        $sessionstart = $result['sessions'][0]['sessionstart'];
+        $sessionend = $result['sessions'][0]['sessionend'];
+        $result = mod_chat_external::get_session_messages($chat->id, $sessionstart, $sessionend);
+        $result = external_api::clean_returnvalue(mod_chat_external::get_session_messages_returns(), $result);
+        $this->assertCount(5, $result['messages']); // 2 system + 3 personal messages.
+        $found = 0;
+        foreach ($result['messages'] as $message) {
+            if (!$message['issystem']) {
+                if ($message['userid'] == $user1->id) {
+                    if ($message['message'] != 'hello!') {
+                        $this->assertEquals('bye bye!', $message['message']);
+                        $found++;
+                    }
+                } else {
+                    $this->assertEquals($user2->id, $message['userid']);
+                    $this->assertEquals('greetings!', $message['message']);
+                    $found++;
+                }
+            }
+        }
+        $this->assertEquals(2, $found);
+    }
 }
index 587b96e..c313d4a 100644 (file)
@@ -24,7 +24,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2017111301; // The current module version (Date: YYYYMMDDXX).
+$plugin->version   = 2017111303; // The current module version (Date: YYYYMMDDXX).
 $plugin->requires  = 2017110800; // Requires this Moodle version.
 $plugin->component = 'mod_chat'; // Full name of the plugin (used for diagnostics).
 $plugin->cron      = 300;
index 8dfabf3..74ef55c 100644 (file)
@@ -145,7 +145,7 @@ if (has_capability('mod/chat:chat', $context)) {
     echo '</p>';
 
     if ($chat->studentlogs or has_capability('mod/chat:readlog', $context)) {
-        if ($msg = $DB->get_records_select('chat_messages', "chatid = ? $groupselect", array($chat->id))) {
+        if ($msg = chat_get_session_messages($chat->id, $currentgroup)) {
             echo '<p>';
             echo html_writer::link(new moodle_url('/mod/chat/report.php', array('id' => $cm->id)),
                                    get_string('viewreport', 'chat'));
index bb5fbbc..9389dab 100644 (file)
@@ -40,7 +40,7 @@ Feature: Students can edit or delete their forum posts within a set time limit
     And I press "Continue"
     Then I should not see "Forum post subject"
 
-  @javascript
+  @javascript @block_recent_activity
   Scenario: Time limit expires
     Given I log out
     And I log in as "admin"
@@ -52,6 +52,8 @@ Feature: Students can edit or delete their forum posts within a set time limit
     And I log out
     And I log in as "student1"
     And I am on "Course 1" course homepage
+    And I should see "New forum posts:" in the "Recent activity" "block"
+    And I should see "Forum post subject" in the "Recent activity" "block"
     When I wait "61" seconds
     And I follow "Forum post subject"
     Then I should not see "Edit" in the "region-main" "region"
diff --git a/mod/glossary/tests/behat/import_entries.feature b/mod/glossary/tests/behat/import_entries.feature
new file mode 100644 (file)
index 0000000..eab2c79
--- /dev/null
@@ -0,0 +1,34 @@
+@mod @mod_glossary
+Feature: Importing glossary entries
+  In order to add glossary entries by bulk
+  As a teacher
+  I need to be able to import glossary entries from a file
+
+  Background:
+    Given the following "courses" exist:
+      | fullname | shortname | category |
+      | Course 1 | C1 | 0 |
+    And the following "users" exist:
+      | username    | firstname | lastname | email            |
+      | teacher1    | Terry1    | Teacher1 | teacher1@example.com |
+    And the following "course enrolments" exist:
+      | user        | course | role           |
+      | teacher1    | C1     | editingteacher |
+    And the following "activities" exist:
+      | activity | course | idnumber  | name        |
+      | glossary | C1     | glossary1 | Glossary 1  |
+
+  @javascript @block_recent_activity
+  Scenario: Importing glossary entries and checking the Recent activity block
+    Given I log in as "teacher1"
+    And I am on "Course 1" course homepage with editing mode on
+    And I add the "Recent activity" block
+    And I follow "Glossary 1"
+    And I navigate to "Import entries" in current page administration
+    And I upload "mod/glossary/tests/fixtures/texfilter_glossary_en.xml" file to "File to import" filemanager
+    When I press "Submit"
+    Then I should see "103" in the "Total entries:" "table_row"
+    And I should see "103" in the "Imported entries:" "table_row"
+    And I am on "Course 1" course homepage
+    And I should see "Added Glossary" in the "Recent activity" "block"
+    And I should see "New glossary entries:" in the "Recent activity" "block"
diff --git a/mod/glossary/tests/fixtures/texfilter_glossary_en.xml b/mod/glossary/tests/fixtures/texfilter_glossary_en.xml
new file mode 100644 (file)
index 0000000..8941672
--- /dev/null
@@ -0,0 +1,1749 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<GLOSSARY>
+  <INFO>
+    <NAME>&lt;b&gt;TeX Filter Reference English en_20040328_SR02&lt;/b&gt;</NAME>
+    <INTRO>
+&lt;br /&gt;&lt;span style=&quot;color: rgb(128, 0, 0);&quot;&gt;Find entries by searching for (partial) words or symbols. Enable &lt;b&gt;&amp;quot;search full text&amp;quot;&lt;/b&gt; to search not only in the &amp;quot;concept field&amp;quot; but also in the description / definition field.
+
+Are you &lt;b&gt;new to the TeX Filter or to TeX?&lt;/b&gt; Then you might want to read step by step through the different categories, starting with category &lt;b&gt;&amp;quot;01 Getting started&amp;quot;&lt;/b&gt;.&lt;/span&gt;&lt;br /&gt;
+&lt;p style=&quot;font-size: 8pt;&quot;&gt;&lt;u&gt;Test your TeX-Syntax:&lt;/u&gt;&lt;br /&gt;
+&lt;a href=&quot;http://moodle.org/filter/tex/texdebug.php&quot; target=&quot;_new&quot;&gt;Test your syntax interactively here&lt;/a&gt;. This Link is also available on each moodle site itself: &amp;quot;&lt;i&gt;../filter/tex/texdebug.php&lt;/i&gt;&amp;quot;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot;font-size: 8pt;&quot;&gt;&lt;u&gt;About the TeX filter:&lt;/u&gt;&lt;br /&gt;The TeX filter for Moodle has been implemented by &lt;a href=&quot;mailto:fiedorow@math.ohio-state.edu&quot;&gt;Zbigniew Fiedorow&lt;/a&gt;. It is based on &lt;a href=&quot;http://www.forkosh.com&quot; target=&quot;_new&quot;&gt;John Forkosh's mimeTeX&lt;/a&gt; 1.40.&lt;br /&gt;&lt;u&gt;Contacts:&lt;/u&gt;&lt;br /&gt;For suggestions, critics, help etc. about &lt;i&gt;this reference&lt;/i&gt; visit the &lt;a href=&quot;http://emathpool.net&quot;&gt;eMathPool&lt;/a&gt; website or &lt;a href=&quot;mailto:andreas.leiser@emathpool.net&quot;&gt;send me&lt;/a&gt; an email.&lt;br /&gt;For any other information about the TeX filter or about moodle, please visit the &lt;a href=&quot;http://moodle.org&quot;&gt;moodle&lt;/a&gt; website.&lt;br /&gt;
+&lt;u&gt;Contributions by:&lt;/u&gt;
+Andreas Leiser, Mark Burnet&lt;/p&gt;</INTRO>
+    <STUDENTCANPOST>1</STUDENTCANPOST>
+    <ALLOWDUPLICATEDENTRIES>0</ALLOWDUPLICATEDENTRIES>
+    <DISPLAYFORMAT>2</DISPLAYFORMAT>
+    <SHOWSPECIAL>1</SHOWSPECIAL>
+    <SHOWALPHABET>1</SHOWALPHABET>
+    <SHOWALL>1</SHOWALL>
+    <ALLOWCOMMENTS>1</ALLOWCOMMENTS>
+    <USEDYNALINK>0</USEDYNALINK>
+    <DEFAULTAPPROVAL>0</DEFAULTAPPROVAL>
+    <GLOBALGLOSSARY>0</GLOBALGLOSSARY>
+    <ENTBYPAGE>5</ENTBYPAGE>
+    <ENTRIES>
+      <ENTRY>
+        <CONCEPT>\small</CONCEPT>
+        <DEFINITION>&lt;ul&gt;&lt;li&gt;\small &lt;/li&gt;&lt;li&gt;&lt;u&gt;Ex.:&lt;/u&gt; $$$\small~3x$$$ gives $$\small~3x$$&lt;/li&gt;&lt;/ul&gt;</DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <ALIASES>
+          <ALIAS>
+            <NAME>font size,</NAME>
+          </ALIAS>
+        </ALIASES>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>03  Font Styles</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>\normalsize</CONCEPT>
+        <DEFINITION>&lt;ul&gt;&lt;li&gt;Everthing following the \normalsize command will be output in the smallest predefined font size until the system encounters another font size command. &lt;/li&gt;&lt;li&gt;\normalsize is the default font size, i.e. the size automatically chosen if there is no font size command&lt;/li&gt;&lt;li&gt;&lt;u&gt;Ex.:&lt;/u&gt; $$$\normalsize~3x$$$ gives $$\normalsize~3x$$&lt;/li&gt;&lt;/ul&gt;</DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <ALIASES>
+          <ALIAS>
+            <NAME>font size,</NAME>
+          </ALIAS>
+        </ALIASES>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>03  Font Styles</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>\hspace{n}</CONCEPT>
+        <DEFINITION>&lt;ul&gt;&lt;li&gt;inserts a space of n pixels&lt;/li&gt;&lt;li&gt;&lt;u&gt;Ex.:&lt;/u&gt; $$$f(x)\hspace{6}=\hspace{6}0$$$ gives $$f(x)\hspace{6}=\hspace{6}0$$ &lt;/li&gt;&lt;li&gt;can be combined with the preceding command \unitlength{m}(default: m=1px) , which defines the applied unit&lt;/li&gt;&lt;li&gt;&lt;u&gt;Ex.:&lt;/u&gt; $$$\unitlength{20}a\hspace{2}b$$$ gives $$\unitlength{20}a\hspace{2}b$$ , i.e. a space of 20x2=40px&lt;/li&gt;&lt;/ul&gt;</DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <ALIASES>
+          <ALIAS>
+            <NAME>math spaces</NAME>
+          </ALIAS>
+        </ALIASES>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>05  Spaces</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>\tiny</CONCEPT>
+        <DEFINITION>&lt;ul&gt;&lt;li&gt;Everthing following the \tiny command will be output in the smallest predefined font size until the system encounters another font size command.&lt;/li&gt;&lt;li&gt;&lt;u&gt;Ex.:&lt;/u&gt; $$$\tiny~3x$$$ gives $$\tiny~3x$$&lt;/li&gt;&lt;/ul&gt;</DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <ALIASES>
+          <ALIAS>
+            <NAME>font size,</NAME>
+          </ALIAS>
+        </ALIASES>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>03  Font Styles</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>\_  (where _ is blank)</CONCEPT>
+        <DEFINITION>&lt;ul&gt;&lt;li&gt;Ordinary whitespace to be used after a dot not denoting the end of a sentence&lt;/li&gt;&lt;li&gt;After commands without parameters use \~ (tilde) instead in order to avoid browser specific problems&lt;/li&gt;&lt;/ul&gt;</DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <ALIASES>
+          <ALIAS>
+            <NAME>math spaces, whitespace, blank space</NAME>
+          </ALIAS>
+        </ALIASES>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>05  Spaces</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>\/  (backslash slash)</CONCEPT>
+        <DEFINITION>&lt;ul&gt;&lt;li&gt;\/ (backslash slash) avoids ligatures &lt;/li&gt;&lt;li&gt;&lt;u&gt;Ex.:&lt;/u&gt; $$$V\/A$$$ gives $$V\/A$$ in contrast to $$$VA$$$ which gives $$VA$$&lt;/li&gt;&lt;/ul&gt;</DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <ALIASES>
+          <ALIAS>
+            <NAME>math spaces, ligature</NAME>
+          </ALIAS>
+        </ALIASES>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>05  Spaces</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>\quad</CONCEPT>
+        <DEFINITION>&lt;ul&gt;&lt;li&gt;inserts a space of current character set size&lt;/li&gt;&lt;li&gt;&lt;u&gt;Ex.:&lt;/u&gt; $$$a\quad~b$$$ gives $$a\quad~b$$&lt;/li&gt;&lt;/ul&gt;</DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <ALIASES>
+          <ALIAS>
+            <NAME>math spaces</NAME>
+          </ALIAS>
+        </ALIASES>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>05  Spaces</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>\;</CONCEPT>
+        <DEFINITION>&lt;ul&gt;&lt;li&gt;\; (backslash semicolon) inserts the third smallest predefined space in a formula &lt;/li&gt;&lt;li&gt;Equivalent: \hspace{6} &lt;/li&gt;&lt;li&gt;&lt;u&gt;Ex.:&lt;/u&gt; $$$a\;b$$$ gives $$a\;b$$ &lt;/li&gt;&lt;li&gt;&lt;u&gt;Ex.:&lt;/u&gt; $$$a~\hspace{6}~b$$$ gives also $$a~\hspace{6}~b$$&lt;/li&gt;&lt;/ul&gt;</DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <ALIASES>
+          <ALIAS>
+            <NAME>math spaces</NAME>
+          </ALIAS>
+        </ALIASES>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>05  Spaces</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>\:</CONCEPT>
+        <DEFINITION>&lt;ul&gt;&lt;li&gt;\: inserts the second smallest predefined space in a formula &lt;/li&gt;&lt;li&gt;Equivalent: \hspace{4}&lt;/li&gt;&lt;li&gt;&lt;u&gt;Ex.:&lt;/u&gt; $$$a\:b$$$ gives $$a\:b$$ &lt;/li&gt;&lt;li&gt;&lt;u&gt;Ex.:&lt;/u&gt; $$$a~\hspace{4}~b$$$ gives also $$a~\hspace{4}~b$$&lt;/li&gt;&lt;/ul&gt;</DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <ALIASES>
+          <ALIAS>
+            <NAME>math spaces</NAME>
+          </ALIAS>
+        </ALIASES>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>05  Spaces</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>\,</CONCEPT>
+        <DEFINITION>&lt;ul&gt;&lt;li&gt;\, inserts the smallest predefined space in a formula&lt;/li&gt;&lt;li&gt;Equivalent: \hspace{2}&lt;/li&gt;&lt;li&gt;&lt;u&gt;Ex.:&lt;/u&gt; $$$a\,b$$$ gives $$a\,b$$&lt;/li&gt;&lt;li&gt;&lt;u&gt;Ex.:&lt;/u&gt; $$$a~\hspace{2}~b$$$ gives also $$a~\hspace{2}~b$$&lt;/li&gt;&lt;/ul&gt;</DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <ALIASES>
+          <ALIAS>
+            <NAME>math spaces</NAME>
+          </ALIAS>
+        </ALIASES>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>05  Spaces</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>math spaces</CONCEPT>
+        <DEFINITION>&lt;p&gt;List of predefined spaces:&lt;/p&gt;&lt;p&gt;&lt;table cellspacing=&quot;0&quot; cellpadding=&quot;1&quot; width=&quot;100%&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;activitylabel&quot;&gt;&lt;table cellspacing=&quot;1&quot; cellpadding=&quot;1&quot; width=&quot;100%&quot; align=&quot;left&quot; border=&quot;1&quot;&gt;&lt;tbody&gt;&lt;tr style=&quot;COLOR: #ffffff; BACKGROUND-COLOR: #aaaaaa&quot; width=&quot;33%&quot;&gt;&lt;td valign=&quot;top&quot; width=&quot;100%&quot; colspan=&quot;3&quot;&gt;&lt;strong&gt;Math Spaces&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr style=&quot;BACKGROUND-COLOR: #dddddd&quot; width=&quot;33%&quot;&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;&lt;strong&gt;Command&lt;/strong&gt;&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;&lt;strong&gt;Example&lt;/strong&gt;&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;&lt;strong&gt;Result&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\, (smallest predefined)&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$a\,b$$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$a\,b$$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\:  (second smallest predefined)&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$a\:b$$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$a\:b$$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\;  (third smallest predefined)&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$a\;b$$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$a\;b$$ &lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\/  (avoiding ligatures)&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$V\/A$$$ instead of $$$VA$$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$V\/A$$ instead of $$VA$$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\quad  (space of current character set size)&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$a\quad~b$$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$a\quad~b$$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\qquad  (double space of current character set size)&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$a\qquad~b$$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$a\qquad~b$$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\_ &lt;em&gt;(where _ is blank!)&lt;/em&gt;&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;&lt;p&gt;$$$a\ b$$$&lt;/p&gt;&lt;p&gt;&lt;font color=&quot;#ff0000&quot;&gt;(whereas $$$a\b$$$ is &lt;em&gt;not&lt;/em&gt; a valid filter expression since the blank space is missing; it is recommended to use the tilde ~ instead of the simple whitespace)&lt;/font&gt;&lt;/p&gt;&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;&lt;p&gt;$$a\ b$$&lt;/p&gt;&lt;p /&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;/td /&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\hspace{n} ,where n positive integer (= n Pixels)&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;&lt;p&gt;$$$a~\hspace{30}~b$$$&lt;/p&gt;&lt;p&gt;$$$a~\hspace{15}~b$$$&lt;/p&gt;&lt;p&gt;$$$a~\hspace{2}~b$$$&lt;/p&gt;&lt;p&gt;$$$a~\hspace{1}~b$$$&lt;/p&gt;&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;&lt;p&gt;$$a~\hspace{30}~b$$&lt;/p&gt;&lt;p&gt;$$a~\hspace{15}~b$$&lt;/p&gt;&lt;p&gt;$$a~\hspace{2}~b$$&lt;/p&gt;&lt;p&gt;$$a~\hspace{1}~b$$&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\unitlength{m}\hspace{n}, changes the default unit length (m=1px) to be applied&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;&lt;p&gt;$$$a~\hspace{2}~b\unitlength{10}~\hspace{2}~c$$$&lt;/p&gt;&lt;p&gt;&lt;em&gt;(second space is 10x2=20px)&lt;/em&gt;&lt;/p&gt;&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$a~\hspace{2}~b\unitlength{10}~\hspace{2}~c$$&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Simple blank spaces and tildes (~) are ignored by the TeX filter and don't produce any space. You must use one of the defined math spaces to get a visible (extra) space.&lt;/p&gt;</DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <ALIASES>
+          <ALIAS>
+            <NAME>spaces in formulas, predefined spaces</NAME>
+          </ALIAS>
+        </ALIASES>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>05  Spaces</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>\~</CONCEPT>
+        <DEFINITION>&lt;ul&gt;&lt;li&gt;In order to prevent some browser specific problems with whitespaces, it is advisable to use ~ (tilde) as the whitespace instead of the normal blank key (in places where whitespaces are mandatory, e.g. after commands).&lt;/li&gt;&lt;li&gt;&lt;u&gt;Ex.:&lt;/u&gt; $$$\frac~xy$$$ to produce $$\frac~xy$$&lt;/li&gt;&lt;li&gt;&lt;u&gt;Ex.:&lt;/u&gt; $$$\sqrt~n$$$ to produce $$\sqrt~n$$&lt;/li&gt;&lt;/ul&gt;</DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <ALIASES>
+          <ALIAS>
+            <NAME>blank space, blank key, required whitespace</NAME>
+          </ALIAS>
+        </ALIASES>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>05  Spaces</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>contour integral</CONCEPT>
+        <DEFINITION>&lt;ul&gt;&lt;li&gt;General syntax for symbols with a kind of lower and upper limits:&lt;/li&gt;&lt;/ul&gt;&lt;p align=&quot;center&quot;&gt;\&lt;em&gt;symbolname&lt;/em&gt;_{&lt;em&gt;lowerexpression&lt;/em&gt;}^{&lt;em&gt;upperexpression&lt;/em&gt;}&lt;/p&gt;&lt;ul&gt;&lt;li&gt;In general, there are two ways how these lower and upper expressions can be placed: centered below and above the symbol or in a subscript / superscript manner. In the first case the symbol name is preceded by the word &amp;quot;big&amp;quot;, in the second there is no prefix. &lt;/li&gt;&lt;li&gt;Syntax for the contour integral symbol:&lt;/li&gt;&lt;/ul&gt;&lt;p align=&quot;center&quot;&gt;$$$\bigoint_{0}^{\infty}$$$   gives   &lt;/p&gt;&lt;p align=&quot;center&quot;&gt;$$\bigoint_{0}^{\infty}$$ &lt;/p&gt;&lt;p align=&quot;center&quot;&gt;and&lt;/p&gt;&lt;p align=&quot;center&quot;&gt;$$$\oint_{0}^{\infty}$$$   gives &lt;/p&gt;&lt;p align=&quot;center&quot;&gt;$$\oint_{0}^{\infty}$$&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;div align=&quot;left&quot;&gt;Use font size commands for a nicer picture:&lt;/div&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p align=&quot;left&quot; /&gt;&lt;p align=&quot;center&quot;&gt;$$$\LARGE\bigoint_{\small0}^{\small\infty}$$$   gives   &lt;/p&gt;&lt;p /&gt;&lt;p align=&quot;center&quot;&gt;$$\LARGE\bigoint_{\small0}^{\small\infty}$$ &lt;/p&gt;&lt;p align=&quot;left&quot; /&gt;&lt;p align=&quot;center&quot;&gt;and&lt;/p&gt;&lt;p align=&quot;center&quot;&gt;$$$\large\oint_{\small0}^{\small\infty}$$$   gives &lt;/p&gt;&lt;p /&gt;&lt;p align=&quot;center&quot;&gt;$$\large\oint_{\small0}^{\small\infty}$$&lt;/p&gt;</DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>06  Symbols</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>sum (summation)</CONCEPT>
+        <DEFINITION>&lt;ul&gt;&lt;li&gt;General syntax for symbols with a kind of lower and upper limits:&lt;/li&gt;&lt;/ul&gt;&lt;p align=&quot;center&quot;&gt;\&lt;em&gt;symbolname&lt;/em&gt;_{&lt;em&gt;lowerexpression&lt;/em&gt;}^{&lt;em&gt;upperexpression&lt;/em&gt;}&lt;/p&gt;&lt;ul&gt;&lt;li&gt;In general, there are two ways how these lower and upper expressions can be placed: centered below and above the symbol or in a subscript / superscript manner. In the first case the symbol name is preceded by the word &amp;quot;big&amp;quot;, in the second there is no prefix.&lt;/li&gt;&lt;li&gt;Syntax for summation symbol:&lt;/li&gt;&lt;/ul&gt;&lt;p align=&quot;center&quot;&gt;$$$\bigsum_{i=k}^{n}$$$   gives   &lt;/p&gt;&lt;p align=&quot;center&quot;&gt;$$\bigsum_{i=k}^{n}$$ &lt;/p&gt;&lt;p align=&quot;center&quot;&gt;and&lt;/p&gt;&lt;p align=&quot;center&quot;&gt;$$$\sum_{i=k}^{n}$$$   gives &lt;/p&gt;&lt;p align=&quot;center&quot;&gt;$$\sum_{i=k}^{n}$$&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;div align=&quot;left&quot;&gt;Use font size commands for a nicer picture:&lt;/div&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p align=&quot;left&quot; /&gt;&lt;p align=&quot;center&quot;&gt;$$$\LARGE\bigsum_{\small{i=1}}^{\small{n}}$$$   gives   &lt;/p&gt;&lt;p /&gt;&lt;p align=&quot;center&quot;&gt;$$\LARGE\bigsum_{\small{i=1}}^{\small{n}}$$ &lt;/p&gt;&lt;p align=&quot;left&quot; /&gt;&lt;p align=&quot;center&quot;&gt;and&lt;/p&gt;&lt;p align=&quot;center&quot;&gt;$$$\large\sum_{\small{i=1}}^{\small{n}}$$$   gives &lt;/p&gt;&lt;p /&gt;&lt;p align=&quot;center&quot;&gt;$$\large\sum_{\small{i=1}}^{\small{n}}$$&lt;/p&gt;</DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <ALIASES>
+          <ALIAS>
+            <NAME>big sum</NAME>
+          </ALIAS>
+        </ALIASES>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>06  Symbols</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>smiley</CONCEPT>
+        <DEFINITION>$$$~\unitlength{.6}~\picture(100){~~(50,50){\circle(99)}~ ~(20,55;50,0;2){+1$\hat\bullet}~~(50,40){\bullet}~~(50,35){\circle(50,25;34)}~ ~(50,35){\circle(50,45;34)}}$$$  is $$~\unitlength{.6}~\picture(100){~~(50,50){\circle(99)}~ ~(20,55;50,0;2){+1$\hat\bullet}~~(50,40){\bullet}~~(50,35){\circle(50,25;34)}~ ~(50,35){\circle(50,45;34)}}$$ </DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <ALIASES>
+          <ALIAS>
+            <NAME>smiley</NAME>
+          </ALIAS>
+        </ALIASES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>formula box</CONCEPT>
+        <DEFINITION>&lt;p&gt;$$$\fbox{x=\frac{1}{2}}$$$  gives &lt;/p&gt;&lt;p align=&quot;center&quot;&gt;$$\fbox{x=\frac{1}{2}}$$ &lt;/p&gt;</DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <ALIASES>
+          <ALIAS>
+            <NAME>fbox</NAME>
+          </ALIAS>
+        </ALIASES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>infinity</CONCEPT>
+        <DEFINITION>&lt;p&gt;$$$\infty$$$  gives $$\infty$$&lt;/p&gt;</DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <ALIASES>
+          <ALIAS>
+            <NAME>infinity</NAME>
+          </ALIAS>
+        </ALIASES>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>06  Symbols</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>mathematics expression</CONCEPT>
+        <DEFINITION>&lt;ul&gt;&lt;li&gt;A valid expression inside the $'s is rendered as mathematics in an inserted gif image.&lt;/li&gt;&lt;li&gt;&lt;u&gt;Ex.:&lt;/u&gt; $$$x=y^2$$$ creates  &lt;/li&gt;&lt;/ul&gt;&lt;p align=&quot;center&quot;&gt;$$x=y^2$$ &lt;/p&gt;</DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <ALIASES>
+          <ALIAS>
+            <NAME>mathematics expression</NAME>
+          </ALIAS>
+        </ALIASES>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>01  Getting started</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>subscript</CONCEPT>
+        <DEFINITION>&lt;ul&gt;&lt;li&gt;The command character &amp;quot;_&amp;quot; triggers subscription of the following expression(s).&lt;/li&gt;&lt;li&gt;For more than one subscripted character put them in braces {...}.&lt;/li&gt;&lt;li&gt;Use font sizing commands for appropriate sizing.&lt;/li&gt;&lt;li&gt;&lt;u&gt;Ex.:&lt;/u&gt;$$$x_1$$$ gives &lt;/li&gt;&lt;/ul&gt;&lt;p style=&quot;TEXT-ALIGN: center&quot;&gt;$$x_1$$&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;u&gt;Ex.:&lt;/u&gt;$$$a_{m+2n}$$$ gives &lt;/li&gt;&lt;/ul&gt;&lt;p style=&quot;TEXT-ALIGN: center&quot;&gt;$$a_{m+2n}$$&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;u&gt;Ex. (with specific sizing):&lt;/u&gt;  $$$x_{\small1}=a_{\small{m+2n}}$$$ gives&lt;/li&gt;&lt;/ul&gt;&lt;p style=&quot;TEXT-ALIGN: center&quot;&gt;$$x_{\small1}=a_{\small{m+2n}}$$&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Combine subscripting with superscripting (command character &amp;quot;^&amp;quot;). &lt;br /&gt;Syntax: Expr_{subExpr}^{supExpr}.&lt;/li&gt;&lt;li&gt;&lt;u&gt;Ex.:&lt;/u&gt; $$$A_{\small{i,j,k}}^{\small{-n+2}}$$$ gives&lt;/li&gt;&lt;/ul&gt;&lt;p style=&quot;TEXT-ALIGN: center&quot;&gt;$$A_{\small{i,j,k}}^{\small{-n+2}}$$&lt;/p&gt;</DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>02  Arithmetic expressions, sub-/superscripts, roots</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>less than</CONCEPT>
+        <DEFINITION>&lt;p&gt;$$$&amp;lt;$$$   gives&lt;/p&gt;&lt;p align=&quot;center&quot;&gt;$$&amp;lt;$$ &lt;/p&gt;</DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <ALIASES>
+          <ALIAS>
+            <NAME>less than</NAME>
+          </ALIAS>
+          <ALIAS>
+            <NAME>&lt;</NAME>
+          </ALIAS>
+        </ALIASES>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>07  Relations</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>not equal</CONCEPT>
+        <DEFINITION>&lt;p&gt;$$$x\neq~y$$$ gives &lt;/p&gt;&lt;p align=&quot;center&quot;&gt;$$x\neq~y$$ &lt;/p&gt;&lt;p align=&quot;left&quot;&gt;&lt;font color=&quot;#ff0000&quot;&gt;&lt;strong&gt;note:&lt;/strong&gt;&lt;/font&gt; \neg produces the logical negation, i.e. $$$\neg~A$$$ gives &lt;/p&gt;&lt;p align=&quot;center&quot;&gt;$$\neg~A$$&lt;/p&gt;</DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <ALIASES>
+          <ALIAS>
+            <NAME>&lt;&gt;</NAME>
+          </ALIAS>
+        </ALIASES>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>07  Relations</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>beta  (lower case greek letter)</CONCEPT>
+        <DEFINITION>$$$\beta$$$ gives $$\beta$$ </DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>06  Symbols</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>superscript</CONCEPT>
+        <DEFINITION>&lt;ul&gt;&lt;li&gt;The command character &amp;quot;^&amp;quot; triggers superscription of the following expression(s).&lt;/li&gt;&lt;li&gt;For more than one superscripted character put them in braces {...}.&lt;/li&gt;&lt;li&gt;Use font sizing commands for appropriate sizing.&lt;/li&gt;&lt;li&gt;&lt;u&gt;Ex.:&lt;/u&gt; $$$x^2$$$ gives &lt;/li&gt;&lt;/ul&gt;&lt;p style=&quot;TEXT-ALIGN: center&quot;&gt;$$x^2$$&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;u&gt;Ex.:&lt;/u&gt; $$$a^{m+2n}$$$ gives &lt;/li&gt;&lt;/ul&gt;&lt;p style=&quot;TEXT-ALIGN: center&quot;&gt;$$a^{m+2n}$$&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;u&gt;Ex. (with specific sizing):&lt;/u&gt; $$$x^{\small2}=a^{\small{m+2n}}$$$ gives&lt;/li&gt;&lt;/ul&gt;&lt;p style=&quot;TEXT-ALIGN: center&quot;&gt;$$x^{\small2}=a^{\small{m+2n}}$$&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Combine superscripting with subscripting (command character &amp;quot;_&amp;quot;). &lt;br /&gt;Syntax: Expr_{subExpr}^{supExpr}.&lt;/li&gt;&lt;li&gt;&lt;u&gt;Ex.:&lt;/u&gt; $$$A_{\small{i,j,k}}^{\small{-n+2}}$$$ gives&lt;/li&gt;&lt;/ul&gt;&lt;p style=&quot;TEXT-ALIGN: center&quot;&gt;$$A_{\small{i,j,k}}^{\small{-n+2}}$$&lt;/p&gt;</DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>02  Arithmetic expressions, sub-/superscripts, roots</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>greater than</CONCEPT>
+        <DEFINITION>&lt;p&gt;$$$x&amp;gt;y$$$  gives &lt;/p&gt;&lt;p align=&quot;center&quot;&gt;$$x&amp;gt;y$$ &lt;/p&gt;</DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <ALIASES>
+          <ALIAS>
+            <NAME>&gt;</NAME>
+          </ALIAS>
+          <ALIAS>
+            <NAME>greater than</NAME>
+          </ALIAS>
+        </ALIASES>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>07  Relations</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>square root</CONCEPT>
+        <DEFINITION>&lt;ul&gt;&lt;li&gt;$$$\sqrt{a}$$$ or $$$\sqrt~a$$$ gives $$\sqrt~a$$&lt;/li&gt;&lt;li&gt;Use braces for terms with more than one character: $$$\sqrt{x+y}$$$ gives&lt;/li&gt;&lt;/ul&gt;&lt;p align=&quot;center&quot;&gt;$$\sqrt{x+y}$$&lt;/p&gt;</DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>02  Arithmetic expressions, sub-/superscripts, roots</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>triangle</CONCEPT>
+        <DEFINITION>$$$\triangle~abc$$$ gives $$\triangle~abc$$ </DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <ALIASES>
+          <ALIAS>
+            <NAME>triangle</NAME>
+          </ALIAS>
+        </ALIASES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>Learning Formula</CONCEPT>
+        <DEFINITION>$$\frac{success}{problem}=~\unitlength{.6}~\picture(100){~~(50,50){\circle(99)}~ ~(20,55;50,0;2){+1$\hat\bullet}~~(50,40){\bullet}~~(50,35){\circle(50,25;34)}~ ~(50,35){\circle(50,45;34)}}$$ </DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <ALIASES>
+          <ALIAS>
+            <NAME>learning formula</NAME>
+          </ALIAS>
+        </ALIASES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>delta (lower case greek letter)</CONCEPT>
+        <DEFINITION>$$$\delta$$$ gives $$\delta $$ </DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <ALIASES>
+          <ALIAS>
+            <NAME>delta</NAME>
+          </ALIAS>
+        </ALIASES>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>06  Symbols</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>TeX</CONCEPT>
+        <DEFINITION>$$TeX$$  notation allows for the expression of ASCII characters to generate formatted graphics output </DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <ALIASES>
+          <ALIAS>
+            <NAME>TeX</NAME>
+          </ALIAS>
+        </ALIASES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>array</CONCEPT>
+        <DEFINITION>&lt;ul&gt;&lt;li&gt;Syntax for an n-dimensional array: &lt;br /&gt;\begin{array}a&lt;sub&gt;1&lt;/sub&gt;&amp;amp;...&amp;amp;a&lt;sub&gt;n&lt;/sub&gt;\end{array}&lt;/li&gt;&lt;li&gt;&lt;u&gt;Ex.:&lt;/u&gt; $$$\(\begin{array}a_{\fs{0}1}\fs{3},&amp;amp;a_{\fs{0}2}\fs{3},&amp;amp;a_{\fs{0}3}\end{array}\)$$$ gives&lt;/li&gt;&lt;/ul&gt;&lt;p align=&quot;center&quot;&gt;$$(\begin{array}a_{\fs{0}1}\fs{3},&amp;amp;a_{\fs{0}2}\fs{3},&amp;amp;a_{\fs{0}3}\end{array})$$&lt;/p&gt;</DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <ALIASES>
+          <ALIAS>
+            <NAME>array, vector, matrix</NAME>
+          </ALIAS>
+        </ALIASES>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>09  Structures</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>triggering the TeX filter</CONCEPT>
+        <DEFINITION>&lt;ul&gt;&lt;li&gt;Two double $'s embracing a valid math expression trigger the filter to generate and insert the formula gif. &lt;/li&gt;&lt;li&gt;&lt;u&gt;Ex.:&lt;/u&gt;  $$$a^2$$$ produces $$a^2$$&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;</DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>1</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <ALIASES>
+          <ALIAS>
+            <NAME>trigger, TeX filter, start filter</NAME>
+          </ALIAS>
+        </ALIASES>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>01  Getting started</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>escaping the TeX filter</CONCEPT>
+        <DEFINITION>&lt;ul&gt;&lt;li&gt;With two triple $'s embracing an expression you can make the filter to escape and the code itself is shown (with two embracing double $'s). &lt;/li&gt;&lt;li&gt;&lt;u&gt;Ex.:&lt;/u&gt; $$$$a^2$$$$ produces $$$a^2$$$, i.e. prevents the filter to render it as a formula gif.&lt;/li&gt;&lt;/ul&gt;</DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <ALIASES>
+          <ALIAS>
+            <NAME>escape; suppress filter; prevent from filtering</NAME>
+          </ALIAS>
+        </ALIASES>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>01  Getting started</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>\large (all lower case letters)</CONCEPT>
+        <DEFINITION>&lt;ul&gt;&lt;li&gt;Everthing following the \large command will be output in the large font size until the system encounters another font size command. &lt;/li&gt;&lt;li&gt;Note: This command is case sensitive, since large, Large and LARGE are different sizes! &lt;/li&gt;&lt;li&gt;&lt;u&gt;Ex.:&lt;/u&gt; $$$\large~3x$$$ gives $$\large~3x$$&lt;/li&gt;&lt;/ul&gt;</DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <ALIASES>
+          <ALIAS>
+            <NAME>font size, fs{3}</NAME>
+          </ALIAS>
+        </ALIASES>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>03  Font Styles</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>\Large  (L capital letter)</CONCEPT>
+        <DEFINITION>&lt;ul&gt;&lt;li&gt;Everthing following the \Large command will be output in the second largest font size until the system encounters another font size command. &lt;/li&gt;&lt;li&gt;Note: This command is case sensitive, since large, Large and LARGE are different sizes!  &lt;/li&gt;&lt;li&gt;&lt;u&gt;Ex.:&lt;/u&gt; $$$\Large~3x$$$ gives $$\Large~3x$$&lt;/li&gt;&lt;/ul&gt;</DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <ALIASES>
+          <ALIAS>
+            <NAME>font size,</NAME>
+          </ALIAS>
+        </ALIASES>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>03  Font Styles</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>\LARGE  (all capital letters)</CONCEPT>
+        <DEFINITION>&lt;ul&gt;&lt;li&gt;Everthing following the \LARGE command will be output in the largest predefined font size until the system encounters another font size command. &lt;/li&gt;&lt;li&gt;Note: This command is case sensitive, since large, Large and LARGE are different sizes!  &lt;/li&gt;&lt;li&gt;&lt;u&gt;Ex.:&lt;/u&gt; $$$\LARGE~3x$$$ gives $$\LARGE~3x$$&lt;/li&gt;&lt;/ul&gt;</DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <ALIASES>
+          <ALIAS>
+            <NAME>font size,</NAME>
+          </ALIAS>
+        </ALIASES>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>03  Font Styles</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>absolute font sizes (overview)</CONCEPT>
+        <DEFINITION>&lt;p&gt;&lt;table cellspacing=&quot;0&quot; cellpadding=&quot;1&quot; width=&quot;100%&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;activitylabel&quot;&gt;&lt;table cellspacing=&quot;1&quot; cellpadding=&quot;1&quot; width=&quot;100%&quot; align=&quot;left&quot; border=&quot;1&quot;&gt;&lt;tbody&gt;&lt;tr style=&quot;COLOR: #ffffff; BACKGROUND-COLOR: #aaaaaa&quot; width=&quot;33%&quot;&gt;&lt;td valign=&quot;top&quot; width=&quot;100%&quot; colspan=&quot;3&quot;&gt;&lt;strong&gt;Absolute Font Sizes&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr style=&quot;BACKGROUND-COLOR: #dddddd&quot; width=&quot;33%&quot;&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;&lt;strong&gt;Command&lt;/strong&gt;&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;&lt;strong&gt;Example&lt;/strong&gt;&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;&lt;strong&gt;Result&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\tiny&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$\tiny 3x$$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$\tiny 3x$$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\small&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$\small 3x$$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$\small 3x$$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\normalsize &lt;em&gt;(default)&lt;/em&gt;&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$\normalsize 3x$$$ or just $$$3x$$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$\normalsize 3x$$ &lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\large&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$\large 3x$$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$\large 3x$$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\Large&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$\Large 3x$$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$\Large 3x$$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\LARGE&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$\LARGE 3x$$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$\LARGE 3x$$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt; &lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt; &lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt; &lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;/td /&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;&lt;p&gt;&lt;font color=&quot;#000000&quot;&gt;\huge and \Huge are &lt;em&gt;not&lt;/em&gt; supported by the mimeTeX filter&lt;/font&gt;&lt;/p&gt;&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt; &lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt; &lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/p&gt;&lt;p /&gt;</DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>03  Font Styles</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>\qquad</CONCEPT>
+        <DEFINITION>&lt;ul&gt;&lt;li&gt;inserts a double space of current character set size&lt;/li&gt;&lt;li&gt;&lt;u&gt;Ex.:&lt;/u&gt; $$$a\qquad~b$$$ gives $$a\qquad~b$$&lt;/li&gt;&lt;/ul&gt;</DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <ALIASES>
+          <ALIAS>
+            <NAME>math space</NAME>
+          </ALIAS>
+        </ALIASES>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>05  Spaces</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>parentheses</CONCEPT>
+        <DEFINITION>&lt;ul&gt;&lt;li&gt;Syntax: \left(...\right) or \(...\)&lt;/li&gt;&lt;li&gt;&lt;u&gt;Ex.:&lt;/u&gt; $$$2a\left(b+c\right)$$$ gives &lt;/li&gt;&lt;/ul&gt;&lt;p align=&quot;center&quot;&gt;$$2a\left(b+c\right)$$&lt;/p&gt;</DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>04  Delimiters (parentheses, braces,...)</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>braces</CONCEPT>
+        <DEFINITION>&lt;ul&gt;&lt;li&gt;Syntax: \left{...\right}&lt;/li&gt;&lt;li&gt;&lt;u&gt;Ex.:&lt;/u&gt; $$$M=\left{a, b, c\right}$$$ gives &lt;/li&gt;&lt;/ul&gt;&lt;p align=&quot;center&quot;&gt;$$M=\left{a, b, c\right}$$&lt;/p&gt;</DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>04  Delimiters (parentheses, braces,...)</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>square bracket</CONCEPT>
+        <DEFINITION>&lt;ul&gt;&lt;li&gt;Synatx: \left[...\right] &lt;/li&gt;&lt;li&gt;&lt;u&gt;Ex.:&lt;/u&gt; $$$\left[a,b\right]$$$ gives $$\left[a,b\right]$$&lt;/li&gt;&lt;/ul&gt;</DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>04  Delimiters (parentheses, braces,...)</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>angle bracket</CONCEPT>
+        <DEFINITION>&lt;ul&gt;&lt;li&gt;Syntax: \left&amp;lt;...\right&amp;gt;&lt;/li&gt;&lt;li&gt;&lt;u&gt;Ex.:&lt;/u&gt; $$$\left&amp;lt;f,g\right&amp;gt;$$$ gives &lt;/li&gt;&lt;/ul&gt;&lt;p align=&quot;center&quot;&gt;$$\left&amp;lt;f,g\right&amp;gt;$$&lt;/p&gt;</DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>04  Delimiters (parentheses, braces,...)</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>vertical line (absolute value, determinant, ...etc. symbol)</CONCEPT>
+        <DEFINITION>&lt;ul&gt;&lt;li&gt;Syntax: \left|...\right| &lt;/li&gt;&lt;li&gt;&lt;u&gt;Ex.:&lt;/u&gt; $$$\left|b-a\right|$$$ gives $$\left|b-a\right|$$ &lt;/li&gt;&lt;li&gt;&lt;u&gt;Ex.:&lt;/u&gt; $$${\rm~det}\left|\begin{array}{cc}a&amp;amp;b\\c&amp;amp;d \end{array}\right|$$$ gives  &lt;/li&gt;&lt;/ul&gt;&lt;p align=&quot;center&quot;&gt;$${\rm~det}\left|\begin{array}{cc}a&amp;amp;b\\c&amp;amp;d \end{array}\right|$$ &lt;/p&gt;&lt;p align=&quot;left&quot;&gt; &lt;br /&gt;(&amp;quot;\rm~something&amp;quot; renders &amp;quot;something&amp;quot; in roman style)&lt;/p&gt;</DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <ALIASES>
+          <ALIAS>
+            <NAME>absolute value symbol, determinant symbol</NAME>
+          </ALIAS>
+        </ALIASES>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>04  Delimiters (parentheses, braces,...)</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>double vertical line  (norm symbol)</CONCEPT>
+        <DEFINITION>&lt;ul&gt;&lt;li&gt;Syntax: \left\|...\right\| &lt;/li&gt;&lt;li&gt;&lt;u&gt;Exp.:&lt;/u&gt; $$$\left\|af\right\| = \left|a\right|\left\|f\right\|$$$ gives &lt;/li&gt;&lt;/ul&gt;&lt;p align=&quot;center&quot;&gt;$$\left\|af\right\| = \left|a\right|\left\|f\right\|$$&lt;/p&gt;</DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <ALIASES>
+          <ALIAS>
+            <NAME>norm</NAME>
+          </ALIAS>
+        </ALIASES>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>04  Delimiters (parentheses, braces,...)</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>left only brace</CONCEPT>
+        <DEFINITION>&lt;ul&gt;&lt;li&gt;Syntax: \left{...\right.  &lt;em&gt;&lt;font color=&quot;#ff0000&quot;&gt;(note the dot at the end!)&lt;/font&gt;&lt;/em&gt;&lt;/li&gt;&lt;li&gt;&lt;font color=&quot;#000000&quot;&gt;&lt;u&gt;Ex.:&lt;/u&gt; $$$f(x)=\left{{x^2, \rm~if x&amp;gt;-1\atop~0, \rm~else}\right.$$$ gives &lt;/font&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p align=&quot;center&quot;&gt;&lt;font color=&quot;#000000&quot;&gt;$$f(x)=\left{{x^2, \rm~if x&amp;gt;-1\atop~0, \rm~else}\right.$$&lt;/font&gt;&lt;/p&gt;&lt;p&gt;(\rm~something switches to roman style)&lt;/p&gt;</DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>04  Delimiters (parentheses, braces,...)</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>right only brace</CONCEPT>
+        <DEFINITION>&lt;ul&gt;&lt;li&gt;Syntax: \left.{...\right}  &lt;em&gt;&lt;font color=&quot;#ff0000&quot;&gt;(note the dot!)&lt;/font&gt;&lt;/em&gt;&lt;/li&gt;&lt;li&gt;&lt;u&gt;Ex.:&lt;/u&gt; $$$\left.{{\rm~term1\atop\rm~term2}\right}=y$$$ gives &lt;/li&gt;&lt;/ul&gt;&lt;p align=&quot;center&quot;&gt;$$\left.{{\rm~term1\atop\rm~term2}\right}=y$$&lt;/p&gt;&lt;p&gt;(\rm~something switches to roman style)&lt;/p&gt;</DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>04  Delimiters (parentheses, braces,...)</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>delimiters  (overview)</CONCEPT>
+        <DEFINITION>&lt;p&gt;&lt;table cellspacing=&quot;0&quot; cellpadding=&quot;1&quot; width=&quot;100%&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;activitylabel&quot;&gt;&lt;table cellspacing=&quot;1&quot; cellpadding=&quot;1&quot; width=&quot;100%&quot; align=&quot;left&quot; border=&quot;1&quot;&gt;&lt;tbody&gt;&lt;tr style=&quot;COLOR: #ffffff; BACKGROUND-COLOR: #aaaaaa&quot; width=&quot;33%&quot;&gt;&lt;td valign=&quot;top&quot; width=&quot;100%&quot; colspan=&quot;3&quot;&gt;&lt;strong&gt;Delimiters (parentheses, braces, brackets. ...)&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr style=&quot;BACKGROUND-COLOR: #dddddd&quot; width=&quot;33%&quot;&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;&lt;strong&gt;Command&lt;/strong&gt;&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;&lt;strong&gt;Example&lt;/strong&gt;&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;&lt;strong&gt;Result&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;&lt;p&gt;\left(... \right)&lt;/p&gt;&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$2\left(a+b\right)$$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$2~\left(a+b\right)$$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\left[... \right]&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$\left[a^2+b^2~\right]$$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$\left[a^2+b^2~\right]$$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\left{... \right}&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$\left{x^2, x^3, x^4,... \right}$$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$\left{x^2, x^3, x^4,... \right}$$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\left\langle... \right\rangle&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$\left\langle a,b~\right\rangle$$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$\left\langle a,b~\right\rangle$$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\left| ... \right| &lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$\det\left|\array{a&amp;amp;b\\c&amp;amp;d}\right| $$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$\det\left|\array{a&amp;amp;b\\c&amp;amp;d}\right| $$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\left\| ... \right\| &lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$\left\|f~\right\|$$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$\left\|f~\right\|$$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;&lt;p&gt;\left{ ... \right&lt;strong&gt;.&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;font color=&quot;#ff0000&quot;&gt;&lt;em&gt;(note the dot!)&lt;/em&gt;&lt;/font&gt;&lt;/p&gt;&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;&lt;p&gt;$$$f(x)=\left{{x^2, \rm~if x&amp;gt;-1\atop~0, \rm~else}\right.$$$&lt;/p&gt;&lt;p&gt;(\rm switches to roman style)&lt;/p&gt;&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;&lt;p&gt;$$f(x)=\left{{x^2, \rm~if x&amp;gt;-1\atop~0, \rm~else}\right.$$&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;/td /&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;&lt;p&gt;\left&lt;strong&gt;.&lt;/strong&gt;{ ... \right\}&lt;/p&gt;&lt;p&gt;&lt;font color=&quot;#ff0000&quot;&gt;&lt;em&gt;(note the dot!)&lt;/em&gt;&lt;/font&gt;&lt;/p&gt;&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$\left.{{\rm~term1\atop\rm~term2}\right}=y$$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$\left.{{\rm~term1\atop \rm~term2}\right}=y$$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The delimiters are automatically sizes.&lt;/p&gt;</DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>04  Delimiters (parentheses, braces,...)</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>matrix</CONCEPT>
+        <DEFINITION>&lt;ul&gt;&lt;li&gt;An (m,n)-matrix is considered as an array of m*n elements, where the elements of a column are separated by &amp;quot;&amp;amp;&amp;quot; and the rows by &amp;quot;\\&amp;quot;.&lt;/li&gt;&lt;/ul&gt;&lt;ul&gt;&lt;li&gt;Syntax for an (m,n)-matrix: &lt;br /&gt;\begin{array}{&lt;em&gt;colformat}&lt;/em&gt;a&lt;sub&gt;11&lt;/sub&gt;&amp;amp;...&amp;amp;a&lt;sub&gt;1n&lt;/sub&gt;\\a&lt;sub&gt;21&lt;/sub&gt;&amp;amp;...&amp;amp;a&lt;sub&gt;2n&lt;/sub&gt;\\... \\a&lt;sub&gt;m1&lt;/sub&gt;&amp;amp;...&amp;amp;a&lt;sub&gt;mn &lt;/sub&gt;\end{array}&lt;p dir=&quot;ltr&quot; style=&quot;MARGIN-RIGHT: 0px&quot;&gt;where&lt;br /&gt;&lt;em&gt;colformat&lt;/em&gt; defines the format of each of the n columns: l for left, r for right and c for center (hence {ccccc} defines for a (m,5)-matrix in which all columns are centered)&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;u&gt;Ex.:&lt;/u&gt; $$$\left(\begin{array}{lcr}a_{\tiny1}+d &amp;amp; a_{\tiny2}+d &amp;amp; a_{\tiny3}+d \\ b_{\tiny1}&amp;amp; b_{\tiny2}&amp;amp; b_{\tiny3} \\ c_{\tiny1} &amp;amp; c_{\tiny2} &amp;amp; c_{\tiny3} \end{array}\right)$$$ gives&lt;/li&gt;&lt;/ul&gt;&lt;p align=&quot;center&quot;&gt;$$\left(\begin{array}{lcr}a_{\tiny1}+d &amp;amp; a_{\tiny2}+d &amp;amp; a_{\tiny3}+d \\ b_{\tiny1}&amp;amp; b_{\tiny2}&amp;amp; b_{\tiny3} \\ c_{\tiny1} &amp;amp; c_{\tiny2} &amp;amp; c_{\tiny3} \end{array}\right)$$&lt;/p&gt;&lt;p align=&quot;left&quot;&gt;Note in the example above that &amp;quot;lcr&amp;quot; has the effect that column 1 is left aligned, column 2 centered and colums 3 right aligned.&lt;/p&gt;</DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <ALIASES>
+          <ALIAS>
+            <NAME>matrix, array</NAME>
+          </ALIAS>
+        </ALIASES>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>09  Structures</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>constants</CONCEPT>
+        <DEFINITION>&lt;ul&gt;&lt;li&gt;Numbers in formulas are interpreted as constants and they are rendered in non-italic roman font face, which is a widely used convention.&lt;/li&gt;&lt;li&gt;Following this convention, variables are shown in italic.&lt;/li&gt;&lt;li&gt;&lt;u&gt;Exp.:&lt;/u&gt; $$$f(x)=3a+x$$$ gives&lt;/li&gt;&lt;/ul&gt;&lt;p style=&quot;TEXT-ALIGN: center&quot;&gt;$$f(x)=3a+x$$&lt;/p&gt;</DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>02  Arithmetic expressions, sub-/superscripts, roots</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>variables</CONCEPT>
+        <DEFINITION>&lt;ul&gt;&lt;li&gt;Variables in formulas are rendered in italic roman font face, which is a widely used convention.&lt;/li&gt;&lt;li&gt;Following this convention, constants are shown as non-italic.&lt;/li&gt;&lt;li&gt;&lt;u&gt;Exp.:&lt;/u&gt; $$$f(x)=3a+x$$$ gives&lt;/li&gt;&lt;/ul&gt;&lt;p style=&quot;TEXT-ALIGN: center&quot;&gt;$$f(x)=3a+x$$&lt;/p&gt;</DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>02  Arithmetic expressions, sub-/superscripts, roots</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>arithmetic operations</CONCEPT>
+        <DEFINITION>&lt;ul&gt;&lt;li&gt;Type arithmetic operations and &amp;quot;=&amp;quot; as usual.&lt;/li&gt;&lt;li&gt;&lt;u&gt;Exp.:&lt;/u&gt; $$$f(x)=x-2b+(3a/c)$$$ gives&lt;/li&gt;&lt;/ul&gt;&lt;p style=&quot;TEXT-ALIGN: center&quot;&gt;$$f(x)=x-2b+(3a/c)$$&lt;/p&gt;&lt;ul&gt;&lt;li&gt;See also keyword &amp;quot;fraction&amp;quot; for extended capabilities.&lt;/li&gt;&lt;/ul&gt;</DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>02  Arithmetic expressions, sub-/superscripts, roots</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>fraction</CONCEPT>
+        <DEFINITION>&lt;ul&gt;&lt;li&gt;Syntax: \frac{numerator}{denominator}&lt;/li&gt;&lt;li&gt;Use font sizing commands for specific sizing if you don't want the predefined one to be taken.&lt;/li&gt;&lt;li&gt;&lt;u&gt;Ex. (with predefined sizing):&lt;/u&gt; $$$f(x,y)=\frac{2a}{x+y}$$$ gives &lt;/li&gt;&lt;/ul&gt;&lt;p style=&quot;TEXT-ALIGN: center&quot;&gt;$$f(x,y)=\frac{2a}{x+y}$$&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;u&gt;Ex. (with specific sizing):&lt;/u&gt; $$$f(x,y)=\frac{\fs{2}2a}{\fs{2}x+y}$$$ gives &lt;/li&gt;&lt;/ul&gt;&lt;p style=&quot;TEXT-ALIGN: center&quot;&gt;$$f(x,y)=\frac{\fs{2}2a}{\fs{2}x+y}$$&lt;/p&gt;&lt;ul&gt;&lt;li&gt;You may nest fractions as much as you want.&lt;/li&gt;&lt;li&gt;&lt;u&gt;Ex. (nested fractions):&lt;/u&gt; $$$\frac{\frac{a}{x-y}+\frac{b}{x+y}}{1+\frac{a-b}{a+b}}$$$ gives &lt;/li&gt;&lt;/ul&gt;&lt;p style=&quot;TEXT-ALIGN: center&quot;&gt;$$\frac{\frac{a}{x-y}+\frac{b}{x+y}}{1+\frac{a-b}{a+b}}$$&lt;/p&gt;</DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>02  Arithmetic expressions, sub-/superscripts, roots</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>root</CONCEPT>
+        <DEFINITION>&lt;ul&gt;&lt;li&gt;Syntax: \sqrt[n]{arg} or simply  \sqrt{arg} for \sqrt[2]{arg}&lt;/li&gt;&lt;li&gt;&lt;u&gt;Ex.:&lt;/u&gt; $$$\sqrt[3]{8}$$$ gives&lt;/li&gt;&lt;/ul&gt;&lt;p align=&quot;center&quot;&gt;$$\sqrt[3]{8}$$&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;u&gt;Ex.:&lt;/u&gt; $$$\sqrt{-1}$$$ gives&lt;/li&gt;&lt;/ul&gt;&lt;p align=&quot;center&quot;&gt;$$\sqrt{-1}$$&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Nesting of roots (and combining with fractions, ...etc.) are possible.&lt;/li&gt;&lt;li&gt;&lt;u&gt;Ex.:&lt;/u&gt; $$$\sqrt[n]{\frac{x^n-y^n}{1+u^{2n}}}$$$ gives&lt;/li&gt;&lt;/ul&gt;&lt;p align=&quot;center&quot;&gt;$$\sqrt[n]{\frac{x^n-y^n}{1+u^{2n}}}$$&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;u&gt;Ex.:&lt;/u&gt; $$$\sqrt[3]{-q+\sqrt{q^2+p^3}}$$$ gives&lt;/li&gt;&lt;/ul&gt;&lt;p align=&quot;center&quot;&gt;$$\sqrt[3]{-q+\sqrt{q^2+p^3}}$$&lt;/p&gt;</DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <ALIASES>
+          <ALIAS>
+            <NAME>square root</NAME>
+          </ALIAS>
+        </ALIASES>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>02  Arithmetic expressions, sub-/superscripts, roots</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>greater than or equal</CONCEPT>
+        <DEFINITION>&lt;p&gt;$$$x\ge~y$$$ or $$$x\geq~y$$$ gives &lt;/p&gt;&lt;p align=&quot;center&quot;&gt;$$x\ge~y$$ &lt;/p&gt;</DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <ALIASES>
+          <ALIAS>
+            <NAME>&gt;=</NAME>
+          </ALIAS>
+        </ALIASES>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>07  Relations</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>less than or equal</CONCEPT>
+        <DEFINITION>&lt;p&gt;$$$x\le~y$$$ or $$$x\leq~y$$$ gives &lt;/p&gt;&lt;p align=&quot;center&quot;&gt;$$x\le~y$$ &lt;/p&gt;</DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <ALIASES>
+          <ALIAS>
+            <NAME>&lt;=</NAME>
+          </ALIAS>
+        </ALIASES>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>07  Relations</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>greek letters  (overview)</CONCEPT>
+        <DEFINITION>&lt;p&gt;Simply write \&lt;em&gt;greekletter&lt;/em&gt; for lower case and \&lt;em&gt;Greekletter&lt;/em&gt; for upper case&lt;em&gt;.&lt;/em&gt;&lt;/p&gt;&lt;p&gt;Here's a list of all known greek letters (Note: not all &lt;em&gt;upper case&lt;/em&gt; greek letters are known):&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Lower Case Greek Letters:&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;table cellspacing=&quot;1&quot; cellpadding=&quot;5&quot; width=&quot;100%&quot; border=&quot;1&quot;&gt;&lt;tbody&gt;&lt;tr style=&quot;FONT-WEIGHT: bold; BACKGROUND: #dddddd&quot;&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;Command&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;Filter Expression&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;Result&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\alpha&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$\alpha$$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$\alpha$$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\beta&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$\beta$$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$\beta$$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\gamma&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$\gamma$$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$\gamma$$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\delta&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$\delta$$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$\delta$$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\epsilon&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$\epsilon$$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$\epsilon$$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\varepsilon&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$\varepsilon$$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$\varepsilon$$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\zeta&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$\zeta$$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$\zeta$$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\eta&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$\eta$$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$\eta$$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\theta&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$\theta$$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$\theta$$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\vartheta&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$\vartheta$$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$\vartheta$$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\iota&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$\iota$$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$\iota$$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\kappa&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$\kappa$$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$\kappa$$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\lambda&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$\lambda$$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$\lambda$$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\mu&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$\mu$$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$\mu$$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\nu&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$\nu$$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$\nu$$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\xi&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$\xi$$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$\xi$$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;o  &lt;font color=&quot;#ff0000&quot;&gt;(!)&lt;/font&gt;&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$o$$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$o$$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\pi&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$\pi$$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$\pi$$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\varpi&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$\varpi$$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$\varpi$$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\rho&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$\rho$$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$\rho$$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\varrho&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$\varrho$$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$\varrho$$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\sigma&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$\sigma$$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$\sigma$$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\varsigma&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$\varsima$$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$\varsigma$$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\tau&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$\tau$$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$\tau$$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\upsilon&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$\upsilon$$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$\upsilon$$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\phi&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$\phi$$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$\phi$$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\varphi&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$\varphi$$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$\varphi$$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\chi&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$\chi$$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$\chi$$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\psi&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$\psi$$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$\psi$$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\omega&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$\omega$$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$\omega$$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/p&gt;&lt;p /&gt;&lt;p&gt;&lt;b&gt;Upper Case Greek Letters:&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;table cellspacing=&quot;1&quot; cellpadding=&quot;5&quot; width=&quot;100%&quot; border=&quot;1&quot;&gt;&lt;tbody&gt;&lt;tr style=&quot;FONT-WEIGHT: bold; BACKGROUND: #dddddd&quot;&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;Command&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;Filter Expression&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;Result&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\Gamma&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$\Gamma$$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$\Gamma$$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\Delta&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$\Delta$$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$\Delta$$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\Theta&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$\Theta$$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$\Theta$$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\Lambda&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$\Lambda$$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$\Lambda$$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\Xi&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$\Xi$$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$\Xi$$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\Pi&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$\Pi$$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$\Pi$$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\Sigma&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$\Sigma$$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$\Sigma$$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\Upsilon&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$\Upsilon$$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$\Upsilon$$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\Phi&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$\Phi$$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$\Phi$$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\Psi&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$\Psi$$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$\Psi$$&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;\Omega&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$$\Omega$$$&lt;/td&gt;&lt;td valign=&quot;top&quot; width=&quot;33%&quot;&gt;$$\Omega$$&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/p&gt;&lt;p /&gt;</DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>06  Symbols</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>integral</CONCEPT>
+        <DEFINITION>&lt;ul&gt;&lt;li&gt;General syntax for symbols with a kind of lower and upper limits:&lt;/li&gt;&lt;/ul&gt;&lt;p align=&quot;center&quot;&gt;\&lt;em&gt;symbolname&lt;/em&gt;_{&lt;em&gt;lowerexpression&lt;/em&gt;}^{&lt;em&gt;upperexpression&lt;/em&gt;}&lt;/p&gt;&lt;ul&gt;&lt;li&gt;In general, there are two ways how these lower and upper expressions can be placed: centered below and above the symbol or in a subscript / superscript manner. In the first case the symbol name is preceded by the word &amp;quot;big&amp;quot;, in the second there is no prefix. &lt;/li&gt;&lt;li&gt;Syntax for integral symbol:&lt;/li&gt;&lt;/ul&gt;&lt;p align=&quot;center&quot;&gt;$$$\bigint_{0}^{\infty}$$$   gives   &lt;/p&gt;&lt;p align=&quot;center&quot;&gt;$$\bigint_{0}^{\infty}$$ &lt;/p&gt;&lt;p align=&quot;center&quot;&gt;and&lt;/p&gt;&lt;p align=&quot;center&quot;&gt;$$$\int_{0}^{\infty}$$$   gives &lt;/p&gt;&lt;p align=&quot;center&quot;&gt;$$\int_{0}^{\infty}$$&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;div align=&quot;left&quot;&gt;Use font size commands for a nicer picture:&lt;/div&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p align=&quot;left&quot; /&gt;&lt;p align=&quot;center&quot;&gt;$$$\LARGE\bigint_{\small0}^{\small\infty}$$$   gives   &lt;/p&gt;&lt;p /&gt;&lt;p align=&quot;center&quot;&gt;$$\LARGE\bigint_{\small0}^{\small\infty}$$ &lt;/p&gt;&lt;p align=&quot;left&quot; /&gt;&lt;p align=&quot;center&quot;&gt;and&lt;/p&gt;&lt;p align=&quot;center&quot;&gt;$$$\large\int_{\small0}^{\small\infty}$$$   gives &lt;/p&gt;&lt;p /&gt;&lt;p align=&quot;center&quot;&gt;$$\large\int_{\small0}^{\small\infty}$$&lt;/p&gt;</DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <ALIASES>
+          <ALIAS>
+            <NAME>int</NAME>
+          </ALIAS>
+        </ALIASES>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>06  Symbols</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>product</CONCEPT>
+        <DEFINITION>&lt;ul&gt;&lt;li&gt;General syntax for symbols with a kind of lower and upper limits:&lt;/li&gt;&lt;/ul&gt;&lt;p align=&quot;center&quot;&gt;\&lt;em&gt;symbolname&lt;/em&gt;_{&lt;em&gt;lowerexpression&lt;/em&gt;}^{&lt;em&gt;upperexpression&lt;/em&gt;}&lt;/p&gt;&lt;ul&gt;&lt;li&gt;In general, there are two ways how these lower and upper expressions can be placed: centered below and above the symbol or in a subscript / superscript manner. In the first case the symbol name is preceded by the word &amp;quot;big&amp;quot;, in the second there is no prefix. &lt;/li&gt;&lt;li&gt;Syntax for product symbol:&lt;/li&gt;&lt;/ul&gt;&lt;p align=&quot;center&quot;&gt;$$$\bigprod_{i=k}^{n}$$$   gives   &lt;/p&gt;&lt;p align=&quot;center&quot;&gt;$$\bigprod_{i=k}^{n}$$ &lt;/p&gt;&lt;p align=&quot;center&quot;&gt;and&lt;/p&gt;&lt;p align=&quot;center&quot;&gt;$$$\prod_{i=k}^{n}$$$   gives &lt;/p&gt;&lt;p align=&quot;center&quot;&gt;$$\prod_{i=k}^{n}$$&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;div align=&quot;left&quot;&gt;Use font size commands for a nicer picture:&lt;/div&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p align=&quot;left&quot; /&gt;&lt;p align=&quot;center&quot;&gt;$$$\LARGE\bigprod_{\tiny{i=k}}^{\tiny{n}}$$$   gives   &lt;/p&gt;&lt;p /&gt;&lt;p align=&quot;center&quot;&gt;$$\LARGE\bigprod_{\tiny{i=k}}^{\tiny{n}}$$  &lt;/p&gt;&lt;p align=&quot;left&quot; /&gt;&lt;p align=&quot;center&quot;&gt;and&lt;/p&gt;&lt;p align=&quot;center&quot;&gt;$$$\large\prod_{\small{i=k}}^{\small{n}}$$$   gives &lt;/p&gt;&lt;p /&gt;&lt;p align=&quot;center&quot;&gt;$$\large\prod_{\small{i=k}}^{\small{n}}$$&lt;/p&gt;</DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>06  Symbols</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>coproduct</CONCEPT>
+        <DEFINITION>&lt;ul&gt;&lt;li&gt;General syntax for symbols with a kind of lower and upper limits:&lt;/li&gt;&lt;/ul&gt;&lt;p align=&quot;center&quot;&gt;\&lt;em&gt;symbolname&lt;/em&gt;_{&lt;em&gt;lowerexpression&lt;/em&gt;}^{&lt;em&gt;upperexpression&lt;/em&gt;}&lt;/p&gt;&lt;ul&gt;&lt;li&gt;In general, there are two ways how these lower and upper expressions can be placed: centered below and above the symbol or in a subscript / superscript manner. In the first case the symbol name is preceded by the word &amp;quot;big&amp;quot;, in the second there is no prefix. &lt;/li&gt;&lt;li&gt;&lt;font color=&quot;#ff0000&quot;&gt;&lt;strong&gt;Note:&lt;/strong&gt; mimeTeX seems currently only to support the \bigcoprod command.&lt;/font&gt;&lt;/li&gt;&lt;li&gt;Syntax for coproduct symbol: &lt;/li&gt;&lt;/ul&gt;&lt;p align=&quot;center&quot;&gt;$$$\bigcoprod_{i=k}^{n}$$$   gives   &lt;/p&gt;&lt;p align=&quot;center&quot;&gt;$$\bigcoprod_{i=k}^{n}$$ &lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;div align=&quot;left&quot;&gt;Use font size commands for a nicer picture:&lt;/div&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p align=&quot;left&quot; /&gt;&lt;p align=&quot;center&quot;&gt;$$$\LARGE\bigcoprod_{\small{i=k}}^{\small~n}$$$   gives   &lt;/p&gt;&lt;p /&gt;&lt;p align=&quot;center&quot;&gt;$$\LARGE\bigcoprod_{\small{i=k}}^{\small~n}$$&lt;/p&gt;</DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <ALIASES>
+          <ALIAS>
+            <NAME>coprod</NAME>
+          </ALIAS>
+        </ALIASES>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>06  Symbols</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>alpha  (lower case greek letter)</CONCEPT>
+        <DEFINITION>$$$\alpha$$$ gives $$\alpha$$ </DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>06  Symbols</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>gamma  (lower case greek letter)</CONCEPT>
+        <DEFINITION>$$$\gamma$$$ gives $$\gamma$$ </DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>06  Symbols</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>epsilon  (lower case greek letter)</CONCEPT>
+        <DEFINITION>$$$\epsilon$$$ gives $$\epsilon$$ </DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>06  Symbols</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>varepsilon  (special lower case greek letter)</CONCEPT>
+        <DEFINITION>$$$\varepsilon$$$ gives $$\varepsilon$$ </DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>06  Symbols</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>zeta  (lower case greek letter)</CONCEPT>
+        <DEFINITION>$$$\zeta$$$ gives $$\zeta$$ </DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>06  Symbols</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>eta  (lower case greek letter)</CONCEPT>
+        <DEFINITION>$$$\eta$$$ gives $$\eta$$ </DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>06  Symbols</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>theta  (lower case greek letter)</CONCEPT>
+        <DEFINITION>$$$\theta$$$ gives $$\theta$$ </DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>06  Symbols</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>vartheta  (special lower case greek letter)</CONCEPT>
+        <DEFINITION>$$$\vartheta$$$ gives $$\vartheta$$ </DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>06  Symbols</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>iota  (lower case greek letter)</CONCEPT>
+        <DEFINITION>$$$\iota$$$ gives $$\iota$$ </DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>06  Symbols</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>kappa</CONCEPT>
+        <DEFINITION>$$$\kappa$$$ gives $$\kappa$$ </DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>06  Symbols</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>lambda  (lower case greek letter)</CONCEPT>
+        <DEFINITION>$$$\lambda$$$ gives $$\lambda$$ </DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>06  Symbols</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>mu  (lower case greek letter)</CONCEPT>
+        <DEFINITION>$$$\mu$$$ gives $$\mu$$ </DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>06  Symbols</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>nu  (lower case greek letter)</CONCEPT>
+        <DEFINITION>$$$\nu$$$ gives $$\nu$$ </DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>06  Symbols</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>xi  (lower case greek letter)</CONCEPT>
+        <DEFINITION>$$$\xi$$$ gives $$\xi$$ </DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>06  Symbols</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>omikron  (lower case greek letter)</CONCEPT>
+        <DEFINITION>&lt;p&gt;&lt;font color=&quot;#ff0000&quot;&gt;$$$o$$$&lt;/font&gt; gives $$o$$ &lt;/p&gt;&lt;p&gt;&lt;em&gt;(note this exceptional syntax!)&lt;/em&gt;&lt;/p&gt;</DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>06  Symbols</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>pi  (lower case greek letter)</CONCEPT>
+        <DEFINITION>$$$\pi$$$ gives $$\pi$$ </DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>06  Symbols</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>varpi  (special lower case greek letter)</CONCEPT>
+        <DEFINITION>$$$\varpi$$$ gives $$\varpi$$ </DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>06  Symbols</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>rho  (lower case greek letter)</CONCEPT>
+        <DEFINITION>$$$\rho$$$ gives $$\rho$$ </DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>06  Symbols</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>varrho  (special lower case greek letter)</CONCEPT>
+        <DEFINITION>$$$\varrho$$$ gives $$\varrho$$ </DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>06  Symbols</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>sigma  (lower case greek letter)</CONCEPT>
+        <DEFINITION>$$$\sigma$$$ gives $$\sigma$$ </DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>06  Symbols</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>varsigma  (special lower greek letter)</CONCEPT>
+        <DEFINITION>$$$\varsigma$$$ gives $$\varsigma$$ </DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>06  Symbols</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>tau  (lower case greek letter)</CONCEPT>
+        <DEFINITION>$$$\tau$$$ gives $$\tau$$ </DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>06  Symbols</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>upsilon  (lower case greek letter)</CONCEPT>
+        <DEFINITION>$$$\upsilon$$$ gives $$\upsilon$$ </DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>06  Symbols</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>phi  (lower case greek letter)</CONCEPT>
+        <DEFINITION>$$$\phi$$$ gives $$\phi$$ </DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>06  Symbols</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>varphi (special lower case greek letter)</CONCEPT>
+        <DEFINITION>$$$\varphi$$$ gives $$\varphi$$ </DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>06  Symbols</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>chi  (lower case greek letter)</CONCEPT>
+        <DEFINITION>$$$\chi$$$ gives $$\chi$$ </DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>06  Symbols</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>psi  (lower case greek letter)</CONCEPT>
+        <DEFINITION>$$$\psi$$$ gives $$\psi$$ </DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>06  Symbols</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>omega  (lower case greek letter)</CONCEPT>
+        <DEFINITION>$$$\omega$$$ gives $$\omega$$ </DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>06  Symbols</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>Gamma  (upper case greek letter)</CONCEPT>
+        <DEFINITION>$$$\Gamma$$$ gives $$\Gamma$$ </DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>06  Symbols</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>Delta  (upper case greek letter)</CONCEPT>
+        <DEFINITION>$$$\Delta$$$ gives $$\Delta$$ </DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>06  Symbols</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>Theta  (upper case greek letter)</CONCEPT>
+        <DEFINITION>$$$\Theta$$$ gives $$\Theta$$ </DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>06  Symbols</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>Lambda  (upper case greek letter)</CONCEPT>
+        <DEFINITION>$$$\Lambda$$$ gives $$\Lambda$$ </DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>06  Symbols</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>Xi  (upper case greek letter)</CONCEPT>
+        <DEFINITION>$$$\Xi$$$ gives $$\Xi$$ </DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>06  Symbols</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>Pi  (upper case greek letter)</CONCEPT>
+        <DEFINITION>$$$\Pi$$$ gives $$\Pi$$ </DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>06  Symbols</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>Sigma  (upper case greek letter)</CONCEPT>
+        <DEFINITION>$$$\Sigma$$$ gives $$\Sigma$$ </DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>06  Symbols</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>Upsilon  (upper case greek letter)</CONCEPT>
+        <DEFINITION>$$$\Upsilon$$$ gives $$\Upsilon$$ </DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>06  Symbols</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>Phi  (upper case greek letter)</CONCEPT>
+        <DEFINITION>$$$\Phi$$$ gives $$\Phi$$ </DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>06  Symbols</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>Psi  (upper case greek letter)</CONCEPT>
+        <DEFINITION>$$$\Psi$$$ gives $$\Psi$$ </DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>06  Symbols</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>Omega  (upper case greek letter)</CONCEPT>
+        <DEFINITION>$$$\Omega$$$ gives $$\Omega$$ </DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>06  Symbols</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>plus minus</CONCEPT>
+        <DEFINITION>$$$a\pm~b$$$ gives $$a\pm~b$$ </DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>06  Symbols</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>minus plus</CONCEPT>
+        <DEFINITION>$$$\mp~a$$$ gives $$\mp~a$$ </DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>06  Symbols</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>times</CONCEPT>
+        <DEFINITION>$$$a\times~b$$$ gives $$a\times~b$$</DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>06  Symbols</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>div (division)</CONCEPT>
+        <DEFINITION>$$$x\div~y$$$ gives $$x\div~y$$</DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>06  Symbols</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>cdot (multiplication)</CONCEPT>
+        <DEFINITION>$$$a\cdot~b$$$ gives $$a\cdot~b$$</DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>06  Symbols</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+      <ENTRY>
+        <CONCEPT>multiplication (with cdot)</CONCEPT>
+        <DEFINITION>$$$a\cdot~b$$$ gives $$a\cdot~b$$ </DEFINITION>
+        <FORMAT>1</FORMAT>
+        <USEDYNALINK>0</USEDYNALINK>
+        <CASESENSITIVE>0</CASESENSITIVE>
+        <FULLMATCH>0</FULLMATCH>
+        <TEACHERENTRY>1</TEACHERENTRY>
+        <ALIASES>
+          <ALIAS>
+            <NAME>cdot</NAME>
+          </ALIAS>
+        </ALIASES>
+        <CATEGORIES>
+          <CATEGORY>
+            <NAME>02  Arithmetic expressions, sub-/superscripts, roots</NAME>
+            <USEDYNALINK>1</USEDYNALINK>
+          </CATEGORY>
+        </CATEGORIES>
+      </ENTRY>
+    </ENTRIES>
+  </INFO>
+</GLOSSARY>
index 658ecb8..144fe2e 100644 (file)
@@ -85,6 +85,10 @@ if ($data = $mform->get_data()) {
     if (!empty($data->existingcategory)) {
         list($categoryid) = explode(',', $data->category);
         $includesubcategories = !empty($data->includesubcategories);
+        if (!$includesubcategories) {
+            // If the chosen category is a top category.
+            $includesubcategories = $DB->record_exists('question_categories', ['id' => $categoryid, 'parent' => 0]);
+        }
         $returnurl->param('cat', $data->category);
 
     } else if (!empty($data->newcategory)) {
index f66832e..7405dda 100644 (file)
@@ -37,7 +37,6 @@ require_once($CFG->libdir.'/formslib.php');
 class quiz_add_random_form extends moodleform {
 
     protected function definition() {
-        global $CFG, $DB;
         $mform =& $this->_form;
         $mform->setDisableShortforms();
 
@@ -49,11 +48,14 @@ class quiz_add_random_form extends moodleform {
                 get_string('randomfromexistingcategory', 'quiz'));
 
         $mform->addElement('questioncategory', 'category', get_string('category'),
-                array('contexts' => $usablecontexts, 'top' => false));
+                array('contexts' => $usablecontexts, 'top' => true));
         $mform->setDefault('category', $this->_customdata['cat']);
 
         $mform->addElement('checkbox', 'includesubcategories', '', get_string('recurse', 'quiz'));
 
+        $tops = question_get_top_categories_for_contexts(array_column($contexts->all(), 'id'));
+        $mform->hideIf('includesubcategories', 'category', 'in', $tops);
+
         $mform->addElement('select', 'numbertoadd', get_string('randomnumber', 'quiz'),
                 $this->get_number_of_questions_to_add_choices());
 
index bd4dcfc..2b7d741 100644 (file)
@@ -177,17 +177,15 @@ foreach ($overrides as $override) {
     // Icons.
     $iconstr = '';
 
-    if ($active) {
-        // Edit.
-        $editurlstr = $overrideediturl->out(true, array('id' => $override->id));
-        $iconstr = '<a title="' . get_string('edit') . '" href="'. $editurlstr . '">' .
-                $OUTPUT->pix_icon('t/edit', get_string('edit')) . '</a> ';
-        // Duplicate.
-        $copyurlstr = $overrideediturl->out(true,
-                array('id' => $override->id, 'action' => 'duplicate'));
-        $iconstr .= '<a title="' . get_string('copy') . '" href="' . $copyurlstr . '">' .
-                $OUTPUT->pix_icon('t/copy', get_string('copy')) . '</a> ';
-    }
+    // Edit.
+    $editurlstr = $overrideediturl->out(true, array('id' => $override->id));
+    $iconstr = '<a title="' . get_string('edit') . '" href="'. $editurlstr . '">' .
+            $OUTPUT->pix_icon('t/edit', get_string('edit')) . '</a> ';
+    // Duplicate.
+    $copyurlstr = $overrideediturl->out(true,
+            array('id' => $override->id, 'action' => 'duplicate'));
+    $iconstr .= '<a title="' . get_string('copy') . '" href="' . $copyurlstr . '">' .
+            $OUTPUT->pix_icon('t/copy', get_string('copy')) . '</a> ';
     // Delete.
     $deleteurlstr = $overridedeleteurl->out(true,
             array('id' => $override->id, 'sesskey' => sesskey()));
diff --git a/mod/quiz/tests/behat/quiz_user_override.feature b/mod/quiz/tests/behat/quiz_user_override.feature
new file mode 100644 (file)
index 0000000..4b7a5bf
--- /dev/null
@@ -0,0 +1,73 @@
+@mod @mod_quiz @javascript
+Feature: Quiz user override
+  In order to grant a student special access to a quiz
+  As a teacher
+  I need to create an override for that user.
+
+  Background:
+    Given the following "users" exist:
+      | username | firstname | lastname | email                |
+      | teacher1 | Teacher   | One      | teacher1@example.com |
+      | student1 | Student   | One      | student1@example.com |
+      | student2 | Student   | Two      | student2@example.com |
+    And the following "courses" exist:
+      | fullname | shortname | category |
+      | Course 1 | C1        | 0        |
+    And the following "course enrolments" exist:
+      | user     | course | role           |
+      | teacher1 | C1     | editingteacher |
+      | student1 | C1     | student        |
+      | student2 | C1     | student        |
+    And the following "question categories" exist:
+      | contextlevel | reference | name           |
+      | Course       | C1        | Test questions |
+    And the following "activities" exist:
+      | activity   | name   | intro              | course | idnumber |
+      | quiz       | Quiz 1 | Quiz 1 description | C1     | quiz1    |
+    And the following "questions" exist:
+      | questioncategory | qtype       | name  | questiontext    |
+      | Test questions   | truefalse   | TF1   | First question  |
+      | Test questions   | truefalse   | TF2   | Second question |
+    And quiz "Quiz 1" contains the following questions:
+      | question | page | maxmark |
+      | TF1      | 1    |         |
+      | TF2      | 1    | 3.0     |
+    And I log in as "teacher1"
+    And I am on "Course 1" course homepage
+
+  Scenario: Add, modify then delete a user override
+    When I follow "Quiz 1"
+    And I navigate to "User overrides" in current page administration
+    And I press "Add user override"
+    And I set the following fields to these values:
+      | Override user        | Student1 |
+      | id_timeclose_enabled | 1        |
+      | timeclose[day]       | 1        |
+      | timeclose[month]     | January  |
+      | timeclose[year]      | 2020     |
+      | timeclose[hour]      | 08       |
+      | timeclose[minute]    | 00       |
+    And I press "Save"
+    And I should see "Wednesday, 1 January 2020, 8:00"
+    Then I click on "Edit" "link" in the "Student One" "table_row"
+    And I set the following fields to these values:
+      | timeclose[year] | 2030 |
+    And I press "Save"
+    And I should see "Tuesday, 1 January 2030, 8:00"
+    And I click on "Delete" "link"
+    And I press "Continue"
+    And I should not see "Student One"
+
+  Scenario: Being able to modify a user override when the quiz is not available to the student
+    Given I follow "Quiz 1"
+    And I navigate to "Edit settings" in current page administration
+    And I expand all fieldsets
+    And I set the field "Availability" to "Hide from students"
+    And I click on "Save and display" "button"
+    When I navigate to "User overrides" in current page administration
+    And I press "Add user override"
+    And I set the following fields to these values:
+      | Override user    | Student1 |
+      | Attempts allowed | 1        |
+    And I press "Save"
+    Then "Edit" "icon" should exist in the "Student One" "table_row"
diff --git a/question/amd/build/edit_tags.min.js b/question/amd/build/edit_tags.min.js
new file mode 100644 (file)
index 0000000..cf0aed5
Binary files /dev/null and b/question/amd/build/edit_tags.min.js differ
diff --git a/question/amd/build/repository.min.js b/question/amd/build/repository.min.js
new file mode 100644 (file)
index 0000000..35de00b
Binary files /dev/null and b/question/amd/build/repository.min.js differ
diff --git a/question/amd/build/selectors.min.js b/question/amd/build/selectors.min.js
new file mode 100644 (file)
index 0000000..ffe642d
Binary files /dev/null and b/question/amd/build/selectors.min.js differ
diff --git a/question/amd/src/edit_tags.js b/question/amd/src/edit_tags.js
new file mode 100644 (file)
index 0000000..83d5f9a
--- /dev/null
@@ -0,0 +1,229 @@
+// 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/>.
+
+/**
+ * A javascript module to handle question tags editing.
+ *
+ * @module     core_question/edit_tags
+ * @copyright  2018 Simey Lameze <simey@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+define([
+            'jquery',
+            'core/fragment',
+            'core/str',
+            'core/modal_events',
+            'core/modal_factory',
+            'core/notification',
+            'core/custom_interaction_events',
+            'core_question/repository',
+            'core_question/selectors',
+        ],
+        function(
+            $,
+            Fragment,
+            Str,
+            ModalEvents,
+            ModalFactory,
+            Notification,
+            CustomEvents,
+            Repository,
+            QuestionSelectors
+        ) {
+
+    /**
+     * Enable the save button in the footer.
+     *
+     * @param {object} root The container element.
+     * @method enableSaveButton
+     */
+    var enableSaveButton = function(root) {
+        root.find(QuestionSelectors.actions.save).prop('disabled', false);
+    };
+
+    /**
+     * Disable the save button in the footer.
+     *
+     * @param {object} root The container element.
+     * @method disableSaveButton
+     */
+    var disableSaveButton = function(root) {
+        root.find(QuestionSelectors.actions.save).prop('disabled', true);
+    };
+
+    /**
+     * Get the serialised form data.
+     *
+     * @method getFormData
+     * @param {object} modal The modal object.
+     * @return {string} serialised form data
+     */
+    var getFormData = function(modal) {
+        return modal.getBody().find('form').serialize();
+    };
+
+    /**
+     * Set the element state to loading.
+     *
+     * @param {object} root The container element
+     * @method startLoading
+     */
+    var startLoading = function(root) {
+        var loadingIconContainer = root.find(QuestionSelectors.containers.loadingIcon);
+
+        loadingIconContainer.removeClass('hidden');
+    };
+
+    /**
+     * Remove the loading state from the element.
+     *
+     * @param {object} root The container element
+     * @method stopLoading
+     */
+    var stopLoading = function(root) {
+        var loadingIconContainer = root.find(QuestionSelectors.containers.loadingIcon);
+
+        loadingIconContainer.addClass('hidden');
+    };
+
+    /**
+     * Register event listeners for the module.
+     *
+     * @param {object} root The calendar root element
+     */
+    var registerEventListeners = function(root) {
+        var modalPromise = ModalFactory.create(
+            {
+                type: ModalFactory.types.SAVE_CANCEL,
+                large: false
+            },
+            [root, QuestionSelectors.actions.edittags]
+        ).then(function(modal) {
+            // All of this code only executes once, when the modal is
+            // first created. This allows us to add any code that should
+            // only be run once, such as adding event handlers to the modal.
+            Str.get_string('questiontags', 'question')
+                .then(function(string) {
+                    modal.setTitle(string);
+                    return string;
+                })
+                .fail(Notification.exception);
+
+            modal.getRoot().on(ModalEvents.save, function(e) {
+                var form = modal.getBody().find('form');
+                form.submit();
+                e.preventDefault();
+            });
+
+            modal.getRoot().on('submit', 'form', function(e) {
+                save(modal, root).then(function() {
+                    modal.hide();
+                    return;
+                }).fail(Notification.exception);
+
+                // Stop the form from actually submitting and prevent it's
+                // propagation because we have already handled the event.
+                e.preventDefault();
+                e.stopPropagation();
+            });
+
+            return modal;
+        });
+
+        // We need to add an event handler to the tags link because there are
+        // multiple links on the page and without adding a listener we don't know
+        // which one the user clicked on the show the modal.
+        root.on(CustomEvents.events.activate, QuestionSelectors.actions.edittags, function(e) {
+            var currentTarget = $(e.currentTarget);
+
+            var questionId = currentTarget.data('questionid'),
+                canEdit = !!currentTarget.data('canedit'),
+                contextId = currentTarget.data('contextid');
+
+            // This code gets called each time the user clicks the tag link
+            // so we can use it to reload the contents of the tag modal.
+            modalPromise.then(function(modal) {
+                // Display spinner and disable save button.
+                disableSaveButton(root);
+                startLoading(root);
+
+                var args = {
+                    id: questionId
+                };
+
+                var tagsFragment = Fragment.loadFragment('question', 'tags_form', contextId, args);
+                modal.setBody(tagsFragment);
+
+                tagsFragment.then(function() {
+                        enableSaveButton(root);
+                        return;
+                    })
+                    .always(function() {
+                        // Always hide the loading spinner when the request
+                        // has completed.
+                        stopLoading(root);
+                        return;
+                    })
+                .fail(Notification.exception);
+
+                // Show or hide the save button depending on whether the user
+                // has the capability to edit the tags.
+                if (canEdit) {
+                    modal.getRoot().find(QuestionSelectors.actions.save).show();
+                } else {
+                    modal.getRoot().find(QuestionSelectors.actions.save).hide();
+                }
+
+                return modal;
+            }).fail(Notification.exception);
+
+            e.preventDefault();
+        });
+    };
+
+    /**
+     * Send the form data to the server to save question tags.
+     *
+     * @method save
+     * @param {object} modal The modal object.
+     * @param {object} root The container element.
+     * @return {object} A promise
+     */
+    var save = function(modal, root) {
+        // Display spinner and disable save button.
+        disableSaveButton(root);
+        startLoading(root);
+
+        var formData = getFormData(modal);
+
+        // Send the form data to the server for processing.
+        return Repository.submitTagCreateUpdateForm(formData)
+            .always(function() {
+                // Regardless of success or error we should always stop
+                // the loading icon and re-enable the buttons.
+                stopLoading(root);
+                enableSaveButton(root);
+                return;
+            })
+            .fail(Notification.exception);
+    };
+
+    return {
+        init: function(root) {
+            root = $(root);
+            registerEventListeners(root);
+        }
+    };
+});
diff --git a/question/amd/src/repository.js b/question/amd/src/repository.js
new file mode 100644 (file)
index 0000000..42e7272
--- /dev/null
@@ -0,0 +1,48 @@
+// 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/>.
+
+/**
+ * A javascript module to handle question ajax actions.
+ *
+ * @module     core_question/repository
+ * @class      repository
+ * @package    core_question
+ * @copyright  2017 Simey Lameze <lameze@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+define(['jquery', 'core/ajax'], function($, Ajax) {
+
+    /**
+     * Submit the form data for the question tags form.
+     *
+     * @method submitTagCreateUpdateForm
+     * @param {string} formdata The URL encoded values from the form
+     * @return {promise}
+     */
+    var submitTagCreateUpdateForm = function(formdata) {
+        var request = {
+            methodname: 'core_question_submit_tags_form',
+            args: {
+                formdata: formdata
+            }
+        };
+
+        return Ajax.call([request])[0];
+    };
+
+    return {
+        submitTagCreateUpdateForm: submitTagCreateUpdateForm
+    };
+});
diff --git a/question/amd/src/selectors.js b/question/amd/src/selectors.js
new file mode 100644 (file)
index 0000000..438ecea
--- /dev/null
@@ -0,0 +1,34 @@
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * The purpose of this module is to centralize selectors related to question.
+ *
+ * @module     core_question/question_selectors
+ * @package    core_question
+ * @copyright  2018 Simey Lameze <lameze@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+define([], function() {
+    return {
+        actions: {
+            save: '[data-action="save"]',
+            edittags: '[data-action="edittags"]',
+        },
+        containers: {
+            loadingIcon: '[data-region="overlay-icon-container"]',
+        },
+    };
+});
index e4b1990..10f1544 100644 (file)
@@ -112,8 +112,8 @@ class question_category_list_item extends list_item {
         $item .= format_text($category->info, $category->infoformat,
                 array('context' => $this->parentlist->context, 'noclean' => true));
 
-        // don't allow delete if this is the last category in this context.
-        if (!question_is_only_toplevel_category_in_context($category->id)) {
+        // Don't allow delete if this is the top category, or the last editable category in this context.
+        if ($category->parent && !question_is_only_child_of_top_category_in_context($category->id)) {
             $deleteurl = new moodle_url($this->parentlist->pageurl, array('delete' => $this->id, 'sesskey' => sesskey()));
             $item .= html_writer::link($deleteurl,
                     $OUTPUT->pix_icon('t/delete', $str->delete),
@@ -295,17 +295,19 @@ class question_category_object {
 
     public function edit_single_category($categoryid) {
     /// Interface for adding a new category
-        global $COURSE, $DB;
+        global $DB;
         /// Interface for editing existing categories
-        if ($category = $DB->get_record("question_categories", array("id" => $categoryid))) {
-
+        $category = $DB->get_record("question_categories", array("id" => $categoryid));
+        if (empty($category)) {
+            print_error('invalidcategory', '', '', $categoryid);
+        } else if ($category->parent == 0) {
+            print_error('cannotedittopcat', 'question', '', $categoryid);
+        } else {
             $category->parent = "{$category->parent},{$category->contextid}";
             $category->submitbutton = get_string('savechanges');
             $category->categoryheader = $this->str->edit;
             $this->catform->set_data($category);
             $this->catform->display();
-        } else {
-            print_error('invalidcategory', '', '', $categoryid);
         }
     }
 
@@ -440,7 +442,7 @@ class question_category_object {
 
         // Get the record we are updating.
         $oldcat = $DB->get_record('question_categories', array('id' => $updateid));
-        $lastcategoryinthiscontext = question_is_only_toplevel_category_in_context($updateid);
+        $lastcategoryinthiscontext = question_is_only_child_of_top_category_in_context($updateid);
 
         if (!empty($newparent) && !$lastcategoryinthiscontext) {
             list($parentid, $tocontextid) = explode(',', $newparent);
index 93011aa..14ccf19 100644 (file)
@@ -38,7 +38,6 @@ require_once($CFG->libdir.'/formslib.php');
 class question_category_edit_form extends moodleform {
 
     protected function definition() {
-        global $CFG, $DB;
         $mform    = $this->_form;
 
         $contexts   = $this->_customdata['contexts'];
@@ -46,10 +45,10 @@ class question_category_edit_form extends moodleform {
 
         $mform->addElement('header', 'categoryheader', get_string('addcategory', 'question'));
 
-        $questioncategoryel = $mform->addElement('questioncategory', 'parent', get_string('parentcategory', 'question'),
-                    array('contexts'=>$contexts, 'top'=>true, 'currentcat'=>$currentcat, 'nochildrenof'=>$currentcat));
+        $mform->addElement('questioncategory', 'parent', get_string('parentcategory', 'question'),
+                array('contexts' => $contexts, 'top' => true, 'currentcat' => $currentcat, 'nochildrenof' => $currentcat));
         $mform->setType('parent', PARAM_SEQUENCE);
-        if (question_is_only_toplevel_category_in_context($currentcat)) {
+        if (question_is_only_child_of_top_category_in_context($currentcat)) {
             $mform->hardFreeze('parent');
         }
         $mform->addHelpButton('parent', 'parentcategory', 'question');
index abd7dc6..f37a8ef 100644 (file)
@@ -130,12 +130,11 @@ class category_condition extends condition {
      * @param string $current 'categoryID,contextID'.
      */
     protected function display_category_form($contexts, $pageurl, $current) {
-        global $OUTPUT;
-
         echo \html_writer::start_div('choosecategory');
-        $catmenu = question_category_options($contexts, false, 0, true);
+        $catmenu = question_category_options($contexts, true, 0, true);
         echo \html_writer::label(get_string('selectacategory', 'question'), 'id_selectacategory');
-        echo \html_writer::select($catmenu, 'category', $current, array(), array('class' => 'searchoptions custom-select', 'id' => 'id_selectacategory'));
+        echo \html_writer::select($catmenu, 'category', $current, array(),
+                array('class' => 'searchoptions custom-select', 'id' => 'id_selectacategory'));
         echo \html_writer::end_div() . "\n";
     }
 
diff --git a/question/classes/bank/tags_action_column.php b/question/classes/bank/tags_action_column.php
new file mode 100644 (file)
index 0000000..5f49808
--- /dev/null
@@ -0,0 +1,86 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * The question tags column subclass.
+ *
+ * @package   core_question
+ * @copyright 2018 Simey Lameze <simey@moodle.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace core_question\bank;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Action to add and remove tags to questions.
+ *
+ * @package    core_question
+ * @copyright  2018 Simey Lameze <simey@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class tags_action_column extends action_column_base {
+
+    /**
+     * Return the name for this column.
+     *
+     * @return string
+     */
+    public function get_name() {
+        return 'tagsaction';
+    }
+
+    /**
+     * Display tags column content.
+     *
+     * @param object $question The question database record.
+     * @param string $rowclasses
+     */
+    protected function display_content($question, $rowclasses) {
+        global $DB;
+
+        if (\core_tag_tag::is_enabled('core_question', 'question') &&
+                question_has_capability_on($question, 'view')) {
+
+            $canedit = question_has_capability_on($question, 'edit');
+            $category = $DB->get_record('question_categories', ['id' => $question->category], 'contextid');
+            $url = $this->qbank->edit_question_url($question->id);
+
+            $this->print_tag_icon($question->id, $url, $canedit, $category->contextid);
+        }
+    }
+
+    /**
+     * Build and print the tags icon.
+     *
+     * @param int $id The question ID.
+     * @param string $url Editing question url.
+     * @param bool $canedit Whether the user can edit questions or not.
+     * @param int $contextid Question category context ID.
+     */
+    protected function print_tag_icon($id, $url, $canedit, $contextid) {
+        global $OUTPUT;
+
+        $params = [
+            'data-action' => 'edittags',
+            'data-canedit' => $canedit,
+            'data-contextid' => $contextid,
+            'data-questionid' => $id
+        ];
+
+        echo \html_writer::link($url, $OUTPUT->pix_icon('t/tags', get_string('managetags', 'tag')), $params);
+    }
+}
index 8c4142b..bc65791 100644 (file)
@@ -124,10 +124,9 @@ class view {
 
         if (empty($CFG->questionbankcolumns)) {
             $questionbankcolumns = array('checkbox_column', 'question_type_column',
-                                     'question_name_column', 'edit_action_column', 'copy_action_column',
-                                     'preview_action_column', 'delete_action_column',
-                                     'creator_name_column',
-                                     'modifier_name_column');
+                                     'question_name_column', 'tags_action_column', 'edit_action_column',
+                                     'copy_action_column', 'preview_action_column', 'delete_action_column',
+                                     'creator_name_column', 'modifier_name_column');
         } else {
              $questionbankcolumns = explode(',', $CFG->questionbankcolumns);
         }
@@ -481,6 +480,8 @@ class view {
                 $this->baseurl, $cat, $this->cm,
                 null, $page, $perpage, $showhidden, $showquestiontext,
                 $this->contexts->having_cap('moodle/question:add'));
+
+        $PAGE->requires->js_call_amd('core_question/edit_tags', 'init', ['#questionscontainer']);
     }
 
     protected function print_choose_category_message($categoryandcontext) {
@@ -702,7 +703,7 @@ class view {
         echo '<input type="hidden" name="sesskey" value="'.sesskey().'" />';
         echo \html_writer::input_hidden_params($this->baseurl);
 
-        echo '<div class="categoryquestionscontainer">';
+        echo '<div class="categoryquestionscontainer" id="questionscontainer">';
         $this->start_table();
         $rowcount = 0;
         foreach ($questions as $question) {
index dae3272..6210ff7 100644 (file)
@@ -114,4 +114,79 @@ class core_question_external extends external_api {
             )
         );
     }
+
+    /**
+     * Returns description of method parameters.
+     *
+     * @return external_function_parameters.
+     */
+    public static function submit_tags_form_parameters() {
+        return new external_function_parameters([
+                'formdata' => new external_value(PARAM_RAW, 'The data from the tag form'),
+        ]);
+    }
+
+    /**
+     * Handles the tags form submission.
+     *
+     * @param string $formdata The question tag form data in a URI encoded param string
+     * @return array The created or modified question tag
+     * @throws moodle_exception
+     */
+    public static function submit_tags_form($formdata) {
+        global $USER, $DB, $CFG;
+
+        $data = [];
+        $result = ['status' => false];
+
+        // Parameter validation.
+        $params = self::validate_parameters(self::submit_tags_form_parameters(), ['formdata' => $formdata]);
+        $context = \context_user::instance($USER->id);
+
+        self::validate_context($context);
+        parse_str($params['formdata'], $data);
+
+        if (!empty($data['id'])) {
+            $questionid = clean_param($data['id'], PARAM_INT);
+            $question = $DB->get_record('question', array('id' => $questionid));
+
+            require_once($CFG->libdir . '/questionlib.php');
+            $canedit = question_has_capability_on($question, 'edit');
+
+            require_once($CFG->dirroot . '/question/type/tags_form.php');
+            $mform = new \core_question\form\tags(null, null, 'post', '', null, $canedit, $data);
+
+            if ($validateddata = $mform->get_data()) {
+                // Due to a mform bug, if there's no tags set on the tag element, it submits the name as the value.
+                // The only way to discover is checking if the tag element is an array.
+                if ($canedit) {
+                    if (is_array($validateddata->tags)) {
+                        $categorycontext = context::instance_by_id($validateddata->contextid);
+
+                        core_tag_tag::set_item_tags('core_question', 'question', $validateddata->id,
+                            $categorycontext, $validateddata->tags);
+
+                        $result['status'] = true;
+                    } else {
+                        // If the tags element is not array, this means we don't have any tags to be set.
+                        // This is the only way to assume the user removed all tags from the question.
+                        core_tag_tag::remove_all_item_tags('core_question', 'question', $validateddata->id);
+
+                        $result['status'] = true;
+                    }
+                }
+            }
+        }
+
+        return $result;
+    }
+
+    /**
+     * Returns description of method result value.
+     */
+    public static function  submit_tags_form_returns() {
+        return new external_single_structure([
+                'status' => new external_value(PARAM_BOOL, 'status: true if success')
+        ]);
+    }
 }
index f2ca3ea..6fdb286 100644 (file)
@@ -95,28 +95,42 @@ function get_questions_category( $category, $noparent=false, $recurse=true, $exp
 }
 
 /**
+ * Checks whether this is the only child of a top category in a context.
+ *
  * @param int $categoryid a category id.
- * @return bool whether this is the only top-level category in a context.
+ * @return bool
  */
-function question_is_only_toplevel_category_in_context($categoryid) {
+function question_is_only_child_of_top_category_in_context($categoryid) {
     global $DB;
     return 1 == $DB->count_records_sql("
             SELECT count(*)
-              FROM {question_categories} c1,
-                   {question_categories} c2
-             WHERE c2.id = ?
-               AND c1.contextid = c2.contextid
-               AND c1.parent = 0 AND c2.parent = 0", array($categoryid));
+              FROM {question_categories} c
+              JOIN {question_categories} p ON c.parent = p.id
+              JOIN {question_categories} s ON s.parent = c.parent
+             WHERE c.id = ? AND p.parent = 0", array($categoryid));
+}
+
+/**
+ * Checks whether the category is a "Top" category (with no parent).
+ *
+ * @param int $categoryid a category id.
+ * @return bool
+ */
+function question_is_top_category($categoryid) {
+    global $DB;
+    return 0 == $DB->get_field('question_categories', 'parent', array('id' => $categoryid));
 }
 
 /**
- * Check whether this user is allowed to delete this category.
+ * Ensures that this user is allowed to delete this category.
  *
  * @param int $todelete a category id.
  */
 function question_can_delete_cat($todelete) {
     global $DB;
-    if (question_is_only_toplevel_category_in_context($todelete)) {
+    if (question_is_top_category($todelete)) {
+        print_error('cannotdeletetopcat', 'question');
+    } else if (question_is_only_child_of_top_category_in_context($todelete)) {
         print_error('cannotdeletecate', 'question');
     } else {
         $contextid = $DB->get_field('question_categories', 'contextid', array('id' => $todelete));
index c2650a4..8d0b4f6 100644 (file)
@@ -52,7 +52,7 @@ class core_question_renderer extends plugin_renderer_base {
     public function question_preview_link($questionid, context $context, $showlabel) {
         if ($showlabel) {
             $alt = '';
-            $label = ' ' . get_string('preview');
+            $label = get_string('preview');
             $attributes = array();
         } else {
             $alt = get_string('preview');
index 4f7ce0b..9528995 100644 (file)
@@ -69,7 +69,8 @@ class question_export_form extends moodleform {
         // Export options.
         $mform->addElement('header', 'general', get_string('general', 'form'));
 
-        $mform->addElement('questioncategory', 'category', get_string('exportcategory', 'question'), compact('contexts'));
+        $mform->addElement('questioncategory', 'category', get_string('exportcategory', 'question'),
+                array('contexts' => $contexts, 'top' => true));
  &