Merge branch 'MDL-49581_m30v3' of https://github.com/sbourget/moodle
authorDan Poltawski <dan@moodle.com>
Tue, 28 Jul 2015 09:48:11 +0000 (10:48 +0100)
committerDan Poltawski <dan@moodle.com>
Tue, 28 Jul 2015 09:48:11 +0000 (10:48 +0100)
96 files changed:
admin/tool/capability/yui/build/moodle-tool_capability-search/moodle-tool_capability-search-debug.js
admin/tool/capability/yui/build/moodle-tool_capability-search/moodle-tool_capability-search-min.js
admin/tool/capability/yui/build/moodle-tool_capability-search/moodle-tool_capability-search.js
admin/tool/capability/yui/src/search/js/search.js
auth/ldap/auth.php
auth/shibboleth/index.php
availability/classes/info.php
availability/classes/multiple_messages.php [new file with mode: 0644]
availability/classes/tree.php
availability/condition/completion/tests/behat/conditional_bug.feature [new file with mode: 0644]
availability/renderer.php
availability/tests/tree_test.php
blocks/blog_tags/tests/behat/blogtag.feature [new file with mode: 0644]
blocks/navigation/renderer.php
blocks/tags/tests/behat/coursetags.feature [new file with mode: 0644]
blocks/tags/tests/behat/tagcloud.feature [new file with mode: 0644]
course/moodleform_mod.php
course/togglecompletion.php
enrol/index.php
error/index.php
grade/grading/form/guide/js/guideeditor.js
grade/grading/form/guide/renderer.php
install/lang/bg/admin.php
install/lang/es_mx/langconfig.php
install/lang/mk/install.php
lang/en/cache.php
lang/en/moodle.php
lib/ajax/getsiteadminbranch.php
lib/blocklib.php
lib/classes/session/manager.php
lib/db/caches.php
lib/db/services.php
lib/deprecatedlib.php
lib/form/yui/build/moodle-form-dateselector/moodle-form-dateselector-debug.js
lib/form/yui/build/moodle-form-dateselector/moodle-form-dateselector-min.js
lib/form/yui/build/moodle-form-dateselector/moodle-form-dateselector.js
lib/form/yui/src/dateselector/js/dateselector.js
lib/medialib.php
lib/moodlelib.php
lib/navigationlib.php
lib/outputlib.php
lib/pagelib.php
lib/setuplib.php
lib/tablelib.php
lib/testing/generator/data_generator.php
lib/tests/behat/behat_data_generators.php
lib/tests/medialib_test.php
lib/tests/moodlelib_test.php
lib/upgrade.txt
lib/weblib.php
lib/yui/build/moodle-core-dragdrop/moodle-core-dragdrop-debug.js
lib/yui/build/moodle-core-dragdrop/moodle-core-dragdrop-min.js
lib/yui/build/moodle-core-dragdrop/moodle-core-dragdrop.js
lib/yui/src/dragdrop/js/dragdrop.js
login/index.php
mod/assign/mod_form.php
mod/assign/settings.php
mod/book/styles.css
mod/choice/view.php
mod/forum/lib.php
mod/forum/markposts.php
mod/forum/post.php
mod/forum/renderer.php
mod/forum/settracking.php
mod/forum/subscribe.php
mod/glossary/view.php
mod/lesson/mod_form.php
mod/quiz/renderer.php
mod/resource/view.php
mod/scorm/classes/external.php [new file with mode: 0644]
mod/scorm/db/services.php [new file with mode: 0644]
mod/scorm/lib.php
mod/scorm/locallib.php
mod/scorm/tests/externallib_test.php [new file with mode: 0644]
mod/scorm/tests/lib_test.php [new file with mode: 0644]
mod/scorm/version.php
mod/scorm/view.php
mod/survey/save.php
mod/upgrade.txt
mod/url/view.php
mod/wiki/edit.php
mod/wiki/filesedit.php
mod/workshop/lang/en/workshop.php
mod/workshop/locallib.php
mod/workshop/renderer.php
mod/workshop/styles.css
mod/workshop/tests/behat/workshop_assessment.feature
mod/workshop/view.php
my/index.php
my/lib.php
report/participation/index.php
tag/manage.php
theme/upgrade.txt
user/classes/output/myprofile/manager.php
user/view.php
version.php

index 3beb4f7..ca1f505 100644 (file)
Binary files a/admin/tool/capability/yui/build/moodle-tool_capability-search/moodle-tool_capability-search-debug.js and b/admin/tool/capability/yui/build/moodle-tool_capability-search/moodle-tool_capability-search-debug.js differ
index 8b04092..7e1baef 100644 (file)
Binary files a/admin/tool/capability/yui/build/moodle-tool_capability-search/moodle-tool_capability-search-min.js and b/admin/tool/capability/yui/build/moodle-tool_capability-search/moodle-tool_capability-search-min.js differ
index 3beb4f7..ca1f505 100644 (file)
Binary files a/admin/tool/capability/yui/build/moodle-tool_capability-search/moodle-tool_capability-search.js and b/admin/tool/capability/yui/build/moodle-tool_capability-search/moodle-tool_capability-search.js differ
index 6fbaf67..785263a 100644 (file)
@@ -81,7 +81,6 @@ SEARCH.prototype = {
         div.append(label).append(this.input);
 
         this.select.insert(div, 'before');
-        this.select.one('option').setStyle('display', 'none');
 
         this.input.on('keyup', this.typed, this);
         this.select.on('change', this.validate, this);
index 6090ebd..7b39daf 100644 (file)
@@ -1642,7 +1642,7 @@ class auth_plugin_ldap extends auth_plugin_base {
 
         if (($_SERVER['REQUEST_METHOD'] === 'GET'         // Only on initial GET of loginpage
              || ($_SERVER['REQUEST_METHOD'] === 'POST'
-                 && (get_referer() != strip_querystring(qualified_me()))))
+                 && (get_local_referer() != strip_querystring(qualified_me()))))
                                                           // Or when POSTed from another place
                                                           // See MDL-14071
             && !empty($this->config->ntlmsso_enabled)     // SSO enabled
@@ -1653,13 +1653,15 @@ class auth_plugin_ldap extends auth_plugin_base {
 
             // First, let's remember where we were trying to get to before we got here
             if (empty($SESSION->wantsurl)) {
-                $SESSION->wantsurl = (array_key_exists('HTTP_REFERER', $_SERVER) &&
-                                      $_SERVER['HTTP_REFERER'] != $CFG->wwwroot &&
-                                      $_SERVER['HTTP_REFERER'] != $CFG->wwwroot.'/' &&
-                                      $_SERVER['HTTP_REFERER'] != $CFG->httpswwwroot.'/login/' &&
-                                      $_SERVER['HTTP_REFERER'] != $CFG->httpswwwroot.'/login/index.php' &&
-                                      clean_param($_SERVER['HTTP_REFERER'], PARAM_LOCALURL) != '')
-                    ? $_SERVER['HTTP_REFERER'] : NULL;
+                $SESSION->wantsurl = null;
+                $referer = get_local_referer(false);
+                if ($referer &&
+                        $referer != $CFG->wwwroot &&
+                        $referer != $CFG->wwwroot . '/' &&
+                        $referer != $CFG->httpswwwroot . '/login/' &&
+                        $referer != $CFG->httpswwwroot . '/login/index.php') {
+                    $SESSION->wantsurl = $referer;
+                }
             }
 
             // Now start the whole NTLM machinery.
index ef38337..4e5c92a 100644 (file)
@@ -10,7 +10,7 @@
 
     // Support for WAYFless URLs.
     $target = optional_param('target', '', PARAM_LOCALURL);
-    if (!empty($target)) {
+    if (!empty($target) && empty($SESSION->wantsurl)) {
         $SESSION->wantsurl = $target;
     }
 
index afd1d40..05d82fa 100644 (file)
@@ -713,11 +713,21 @@ abstract class info {
      * that we can guarantee does not happen from within building the modinfo
      * object.
      *
-     * @param string $info Info string
+     * @param \renderable|string $inforenderable Info string or renderable
      * @param int|\stdClass $courseorid
      * @return string Correctly formatted info string
      */
-    public static function format_info($info, $courseorid) {
+    public static function format_info($inforenderable, $courseorid) {
+        global $PAGE;
+
+        // Use renderer if required.
+        if (is_string($inforenderable)) {
+            $info = $inforenderable;
+        } else {
+            $renderer = $PAGE->get_renderer('core', 'availability');
+            $info = $renderer->render($inforenderable);
+        }
+
         // Don't waste time if there are no special tags.
         if (strpos($info, '<AVAILABILITY_') === false) {
             return $info;
diff --git a/availability/classes/multiple_messages.php b/availability/classes/multiple_messages.php
new file mode 100644 (file)
index 0000000..0ba70a7
--- /dev/null
@@ -0,0 +1,70 @@
+<?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/>.
+
+/**
+ * Represents multiple availability messages.
+ *
+ * These are messages like 'Not available until <date>'. This class includes
+ * multiple messages so that they can be rendered into a combined display, e.g.
+ * using bulleted lists.
+ *
+ * The tree structure of this object matches that of the availability
+ * restrictions.
+ *
+ * @package core_availability
+ * @copyright 2015 Andrew Nicols <andrew@nicols.co.uk>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+/**
+ * Represents multiple availability messages.
+ *
+ * These are messages like 'Not available until <date>'. This class includes
+ * multiple messages so that they can be rendered into a combined display, e.g.
+ * using bulleted lists.
+ *
+ * The tree structure of this object matches that of the availability
+ * restrictions.
+ *
+ * @package core_availability
+ * @copyright 2015 Andrew Nicols <andrew@nicols.co.uk>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_availability_multiple_messages implements renderable {
+    /** @var bool True if this object represents the root of the tree */
+    public $root;
+    /** @var bool True if items use the AND operator (false = OR) */
+    public $andoperator;
+    /** @var bool True if this part of the tree is marked 'hide entirely' for non-matching users */
+    public $treehidden;
+    /** @var array Array of child items (may be string or this type) */
+    public $items;
+
+    /**
+     * Constructor.
+     *
+     * @param bool $root True if this object represents the root of the tree
+     * @param bool $andoperator True if items use the AND operator (false = OR)
+     * @param bool $treehidden True if this part of the tree is marked 'hide entirely' for non-matching users
+     * @param array $items Array of items (may be string or this type)
+     */
+    public function __construct($root, $andoperator, $treehidden, array $items) {
+        $this->root = $root;
+        $this->andoperator = $andoperator;
+        $this->treehidden = $treehidden;
+        $this->items = $items;
+    }
+}
index 8849320..ef15c58 100644 (file)
@@ -471,11 +471,10 @@ class tree extends tree_node {
      * @param result $result Result object if this is a student display, else null
      * @param bool $root True if this is the root item
      * @param bool $hidden Staff display; true if this tree has show=false (from parent)
+     * @return string|renderable Information to render
      */
     protected function get_full_information_recursive(
             $not, info $info, result $result = null, $root, $hidden = false) {
-        global $PAGE;
-
         // Get list of children - either full list, or those which are shown.
         $children = $this->children;
         $staff = true;
@@ -550,8 +549,7 @@ class tree extends tree_node {
         }
 
         // Format output for display.
-        $renderer = $PAGE->get_renderer('core', 'availability');
-        return $renderer->multiple_messages($root, $andoperator, $treehidden, $items);
+        return new \core_availability_multiple_messages($root, $andoperator, $treehidden, $items);
     }
 
     /**
diff --git a/availability/condition/completion/tests/behat/conditional_bug.feature b/availability/condition/completion/tests/behat/conditional_bug.feature
new file mode 100644 (file)
index 0000000..b7f6fec
--- /dev/null
@@ -0,0 +1,60 @@
+@availability @availability_completion
+Feature: Confirm that conditions on completion no longer cause a bug
+  In order to use completion conditions
+  As a teacher
+  I need it to not break when I set up certain conditions on some modules
+
+  Background:
+    Given the following "courses" exist:
+      | fullname | shortname | format |
+      | Course 1 | C1        | topics |
+    And the following "users" exist:
+      | username |
+      | teacher1 |
+    And the following "course enrolments" exist:
+      | user     | course | role           |
+      | teacher1 | C1     | editingteacher |
+    And the following config values are set as admin:
+      | enableavailability | 1 |
+      | enablecompletion   | 1 |
+
+  @javascript
+  Scenario: Multiple completion conditions on glossary
+    # Set up course.
+    Given I log in as "teacher1"
+    And I follow "Course 1"
+    And I navigate to "Edit settings" node in "Course administration"
+    And I expand all fieldsets
+    And I set the field "Enable completion tracking" to "Yes"
+    And I press "Save and display"
+    And I turn editing mode on
+
+    # Add a couple of Pages with manual completion.
+    And I add a "Page" to section "1" and I fill the form with:
+      | Name         | Page1 |
+      | Page content | x     |
+    And I add a "Page" to section "1" and I fill the form with:
+      | Name         | Page2 |
+      | Page content | x     |
+
+    # Add a Glossary.
+    When I add a "Glossary" to section "1"
+    And I set the following fields to these values:
+      | Name | TestGlossary |
+    And I expand all fieldsets
+
+    # Add restrictions to the previous Pages being complete.
+    And I press "Add restriction..."
+    And I click on "Activity completion" "button" in the "Add restriction..." "dialogue"
+    And I set the field "Activity or resource" to "Page1"
+    And I press "Add restriction..."
+    And I click on "Activity completion" "button" in the "Add restriction..." "dialogue"
+    And I set the field with xpath "//div[@class='availability-item'][preceding-sibling::div]//select[@name='cm']" to "Page2"
+    And I press "Save and return to course"
+    And I should see "Not available unless:" in the ".activity.glossary" "css_element"
+    And I should see "The activity Page1 is marked complete" in the ".activity.glossary" "css_element"
+    And I should see "The activity Page2 is marked complete" in the ".activity.glossary" "css_element"
+    And I follow "TestGlossary"
+
+    # Behat will automatically check there is no error on this page.
+    Then I should see "TestGlossary"
index b098e81..20fff45 100644 (file)
@@ -40,22 +40,25 @@ class core_availability_renderer extends plugin_renderer_base {
      *
      * This function will not be called unless there are at least two messages.
      *
-     * @param bool $root True if this is a root-level list for an activity
-     * @param bool $andoperator True if the messages are being combined as AND
-     * @param bool $roothidden True if the root level should use 'hidden' message
-     * @param array $messages Messages to render
+     * @param core_availability_multiple_messages $renderable Multiple messages
      * @return string Combined HTML
      */
-    public function multiple_messages($root, $andoperator, $roothidden, array $messages) {
+    public function render_core_availability_multiple_messages(
+            core_availability_multiple_messages $renderable) {
         // Get initial message.
-        $out = get_string('list_' . ($root ? 'root_' : '') .
-                ($andoperator ? 'and' : 'or') . ($roothidden ? '_hidden' : ''),
+        $out = get_string('list_' . ($renderable->root ? 'root_' : '') .
+                ($renderable->andoperator ? 'and' : 'or') . ($renderable->treehidden ? '_hidden' : ''),
                 'availability');
 
         // Make the list.
         $out .= html_writer::start_tag('ul');
-        foreach ($messages as $message) {
-            $out .= html_writer::tag('li', $message);
+        foreach ($renderable->items as $item) {
+            if (is_string($item)) {
+                $str = $item;
+            } else {
+                $str = $this->render($item);
+            }
+            $out .= html_writer::tag('li', $str);
         }
         $out .= html_writer::end_tag('ul');
         return $out;
index b19d62c..ebabc08 100644 (file)
@@ -341,9 +341,15 @@ class tree_testcase extends \advanced_testcase {
      * @param int $userid User id
      */
     protected function get_available_results($structure, \core_availability\info $info, $userid) {
+        global $PAGE;
         $tree = new tree($structure);
         $result = $tree->check_available(false, $info, true, $userid);
-        return array($result->is_available(), $tree->get_result_information($info, $result));
+        $information = $tree->get_result_information($info, $result);
+        if (!is_string($information)) {
+            $renderer = $PAGE->get_renderer('core', 'availability');
+            $information = $renderer->render($information);
+        }
+        return array($result->is_available(), $information);
     }
 
     /**
@@ -389,6 +395,8 @@ class tree_testcase extends \advanced_testcase {
      * Tests the get_full_information() function.
      */
     public function test_get_full_information() {
+        global $PAGE;
+        $renderer = $PAGE->get_renderer('core', 'availability');
         // Setup.
         $info = new \core_availability\mock_info();
 
@@ -417,7 +425,7 @@ class tree_testcase extends \advanced_testcase {
                 self::mock(array('m' => 3)));
         $tree = new tree($structure);
         $this->assertRegExp('~<ul.*<ul.*<li.*1.*<li.*2.*</ul>.*<li.*3~',
-                $tree->get_full_information($info));
+                $renderer->render($tree->get_full_information($info)));
 
         // Test intro messages before list. First, OR message.
         $structure->c = array(
@@ -426,13 +434,13 @@ class tree_testcase extends \advanced_testcase {
         );
         $tree = new tree($structure);
         $this->assertRegExp('~Not available unless any of:.*<ul>~',
-                $tree->get_full_information($info));
+                $renderer->render($tree->get_full_information($info)));
 
         // Now, OR message when not shown.
         $structure->show = false;
         $tree = new tree($structure);
         $this->assertRegExp('~hidden.*<ul>~',
-                $tree->get_full_information($info));
+                $renderer->render($tree->get_full_information($info)));
 
         // AND message.
         $structure->op = '&';
@@ -440,11 +448,11 @@ class tree_testcase extends \advanced_testcase {
         $structure->showc = array(false, false);
         $tree = new tree($structure);
         $this->assertRegExp('~Not available unless:.*<ul>~',
-                $tree->get_full_information($info));
+                $renderer->render($tree->get_full_information($info)));
 
         // Hidden markers on items.
         $this->assertRegExp('~1.*hidden.*2.*hidden~',
-                $tree->get_full_information($info));
+                $renderer->render($tree->get_full_information($info)));
 
         // Hidden markers on child tree and items.
         $structure->c[1] = tree::get_nested_json(array(
@@ -452,11 +460,11 @@ class tree_testcase extends \advanced_testcase {
                 self::mock(array('m' => '3'))), tree::OP_AND);
         $tree = new tree($structure);
         $this->assertRegExp('~1.*hidden.*All of \(hidden.*2.*3~',
-                $tree->get_full_information($info));
+                $renderer->render($tree->get_full_information($info)));
         $structure->c[1]->op = '|';
         $tree = new tree($structure);
         $this->assertRegExp('~1.*hidden.*Any of \(hidden.*2.*3~',
-                $tree->get_full_information($info));
+                $renderer->render($tree->get_full_information($info)));
 
         // Hidden markers on single-item display, AND and OR.
         $structure->showc = array(false);
@@ -480,7 +488,7 @@ class tree_testcase extends \advanced_testcase {
                 self::mock(array('m' => '2'))), tree::OP_AND);
         $tree = new tree($structure);
         $this->assertRegExp('~Not available \(hidden.*1.*2~',
-                $tree->get_full_information($info));
+                $renderer->render($tree->get_full_information($info)));
 
         // Single item tree containing single item.
         unset($structure->c[0]->c[1]);
diff --git a/blocks/blog_tags/tests/behat/blogtag.feature b/blocks/blog_tags/tests/behat/blogtag.feature
new file mode 100644 (file)
index 0000000..88bf5b9
--- /dev/null
@@ -0,0 +1,51 @@
+@block @block_blog_tag @core_blog @core_tag
+Feature: Adding blog tag block
+  In order to search blog post by tag
+  As a user
+  I need to be able to use block blog tag
+
+  Scenario: Adding block blog tag to the course
+    Given the following "users" exist:
+      | username | firstname | lastname | email |
+      | teacher1 | Teacher | 1 | teacher1@example.com |
+      | student1 | Student | 1 | student1@example.com |
+    And the following "courses" exist:
+      | fullname  | shortname |
+      | Course 1  | c1        |
+    And the following "tags" exist:
+      | name         | tagtype  |
+      | Neverusedtag | official |
+    And the following "course enrolments" exist:
+      | user     | course | role           |
+      | teacher1 | c1     | editingteacher |
+      | student1 | c1     | student        |
+    When I log in as "teacher1"
+    And I follow "Course 1"
+    And I turn editing mode on
+    And I add the "Blog tags" block
+
+    And I navigate to "Course blogs" node in "Current course > c1 > Participants"
+    And I follow "Blog about this Course"
+    And I set the following fields to these values:
+      | Entry title                                 | Blog post from teacher    |
+      | Blog entry body                             | Teacher blog post content |
+      | Other tags (enter tags separated by commas) | Cats, dogs                |
+    And I press "Save changes"
+    And I log out
+    And I log in as "student1"
+    And I follow "Course 1"
+    And I navigate to "Course blogs" node in "Current course > c1 > Participants"
+    And I follow "Blog about this Course"
+    And I set the following fields to these values:
+      | Entry title                                 | Blog post from student    |
+      | Blog entry body                             | Student blog post content |
+      | Other tags (enter tags separated by commas) | DOGS, mice                |
+    And I press "Save changes"
+    And I follow "c1"
+    Then I should see "Cats" in the "Blog tags" "block"
+    And I should see "dogs" in the "Blog tags" "block"
+    And I should see "mice" in the "Blog tags" "block"
+    And I click on "Cats" "link" in the "Blog tags" "block"
+    And I should see "Blog post from teacher"
+    And I should see "Teacher blog post content"
+    And I log out
index 74cc088..2a92c9f 100644 (file)
@@ -115,7 +115,7 @@ class block_navigation_renderer extends plugin_renderer_base {
             } else if ($item->action instanceof action_link) {
                 //TODO: to be replaced with something else
                 $link = $item->action;
-                $link->text = $icon.$link->text;
+                $link->text = $icon.html_writer::span($link->text, 'item-content-wrap');
                 $link->attributes = array_merge($link->attributes, $attributes);
                 $content = $this->output->render($link);
             } else if ($item->action instanceof moodle_url) {
diff --git a/blocks/tags/tests/behat/coursetags.feature b/blocks/tags/tests/behat/coursetags.feature
new file mode 100644 (file)
index 0000000..c5f9c55
--- /dev/null
@@ -0,0 +1,64 @@
+@block @block_tags @core_tag
+Feature: Block tags displaying course tags
+  In order to tag courses
+  As a user
+  I need to be able to use the block tags
+
+  Background:
+    Given the following "users" exist:
+      | username | firstname | lastname | email |
+      | teacher1 | Teacher | 1 | teacher1@example.com |
+      | student1 | Student | 1 | student1@example.com |
+      | student2 | Student | 2 | student2@example.com |
+    And the following "courses" exist:
+      | fullname  | shortname |
+      | Course 1  | c1        |
+    And the following "tags" exist:
+      | name         | tagtype  |
+      | Neverusedtag | official |
+    And the following "course enrolments" exist:
+      | user     | course | role           |
+      | teacher1 | c1     | editingteacher |
+      | student1 | c1     | student        |
+      | student2 | c1     | student        |
+    And I log in as "admin"
+    And I set the following administration settings values:
+      | Show course tags | 1 |
+    And I log out
+
+  Scenario: Add Tags block to tag courses in a course
+    When I log in as "teacher1"
+    And I follow "Course 1"
+    And I turn editing mode on
+    And I add the "Tags" block
+    And I log out
+    And I log in as "student1"
+    And I follow "Course 1"
+    And I should not see "Neverusedtag" in the "Tags" "block"
+    And I click on "more..." "link" in the "Tags" "block"
+    And I should not see "Neverusedtag"
+    And I follow "c1"
+    And I set the field "coursetag_new_tag" to "Dogs, Mice"
+    And I press "Add"
+    And I should see "Dogs" in the "Tags" "block"
+    And I should see "Mice" in the "Tags" "block"
+    And I log out
+    And I log in as "student2"
+    And I follow "Course 1"
+    And I should see "Dogs" in the "Tags" "block"
+    And I set the field "coursetag_new_tag" to "Cats, Dogs"
+    And I press "Add"
+    And I should see "Dogs" in the "Tags" "block"
+    And I should see "Cats" in the "Tags" "block"
+    And I click on "more..." "link" in the "Tags" "block"
+    And "Cats" "link" should appear before "Dogs" "link"
+    And "Dogs" "link" should appear before "Mice" "link"
+    And I follow "My tags"
+    And I should see "Dogs"
+    And I should see "Cats"
+    And I should not see "Mice"
+    And I follow "All tags"
+    And I follow "Popularity"
+    And "Mice" "link" should appear before "Dogs" "link"
+    And I should not see "Neverusedtag"
+    And I log out
diff --git a/blocks/tags/tests/behat/tagcloud.feature b/blocks/tags/tests/behat/tagcloud.feature
new file mode 100644 (file)
index 0000000..0265fe9
--- /dev/null
@@ -0,0 +1,59 @@
+@block @block_tags @core_tag
+Feature: Block tags displaying tag cloud
+  In order to view system tags
+  As a user
+  I need to be able to use the block tags
+
+  Background:
+    Given the following "users" exist:
+      | username | firstname | lastname | email |
+      | teacher1 | Teacher | 1 | teacher1@example.com |
+      | student1 | Student | 1 | student1@example.com |
+    And the following "courses" exist:
+      | fullname  | shortname |
+      | Course 1  | c1        |
+    And the following "tags" exist:
+      | name         | tagtype  |
+      | Neverusedtag | official |
+    And the following "course enrolments" exist:
+      | user     | course | role           |
+      | teacher1 | c1     | editingteacher |
+      | student1 | c1     | student        |
+    And I log in as "teacher1"
+    And I follow "Preferences" in the user menu
+    And I follow "Edit profile"
+    And I expand all fieldsets
+    And I set the field "Enter tags separated by commas" to "Dogs, Cats"
+    And I press "Update profile"
+    And I log out
+
+  @javascript
+  Scenario: Add Tags block on a front page
+    When I log in as "admin"
+    And I am on site homepage
+    And I follow "Turn editing on"
+    And I add the "Tags" block
+    And I log out
+    And I am on site homepage
+    Then I should see "Dogs" in the "Tags" "block"
+    And I should see "Cats" in the "Tags" "block"
+    And I should not see "Neverusedtag" in the "Tags" "block"
+    And I click on "Dogs" "link" in the "Tags" "block"
+    And I should see "Log in to the site" in the ".breadcrumb" "css_element"
+
+  @javascript
+  Scenario: Add Tags block in a course
+    When I log in as "teacher1"
+    And I follow "Course 1"
+    And I turn editing mode on
+    And I add the "Tags" block
+    And I log out
+    And I log in as "student1"
+    And I follow "Course 1"
+    Then I should see "Dogs" in the "Tags" "block"
+    And I should see "Cats" in the "Tags" "block"
+    And I should not see "Neverusedtag" in the "Tags" "block"
+    And I click on "Dogs" "link" in the "Tags" "block"
+    And I should see "Users tagged with \"Dogs\": 1"
+    And I should see "Teacher 1"
+    And I log out
index 0f7ab3f..8a13aa8 100644 (file)
@@ -142,6 +142,7 @@ abstract class moodleform_mod extends moodleform {
                     }
                 }
 
+                $hasgradeitems = false;
                 $items = grade_item::fetch_all(array('itemtype'=>'mod', 'itemmodule'=>$modulename,'iteminstance'=>$instance, 'courseid'=>$COURSE->id));
                 //will be no items if, for example, this activity supports ratings but rating aggregate type == no ratings
                 if (!empty($items)) {
@@ -151,6 +152,8 @@ abstract class moodleform_mod extends moodleform {
                             if ($mform->elementExists($elname)) {
                                 $mform->hardFreeze($elname); // prevent removing of existing outcomes
                             }
+                        } else {
+                            $hasgradeitems = true;
                         }
                     }
 
@@ -167,12 +170,17 @@ abstract class moodleform_mod extends moodleform {
                     }
                 }
 
+                if (!$hasgradeitems && $mform->elementExists('gradepass')) {
+                    // Remove form element 'Grade to pass' since there are no grade items (when rating not selected).
+                    $mform->removeElement('gradepass');
+                }
+
                 if ($gradecat === false) {
                     // items and outcomes in different categories - remove the option
                     // TODO: add a "Mixed categories" text instead of removing elements with no explanation
                     if ($mform->elementExists('gradecat')) {
                         $mform->removeElement('gradecat');
-                        if ($this->_features->rating) {
+                        if ($this->_features->rating  && !$mform->elementExists('gradepass')) {
                             //if supports ratings then the max grade dropdown wasnt added so the grade box can be removed entirely
                             $mform->removeElement('modstandardgrade');
                         }
index 075134f..f0c77c9 100644 (file)
@@ -78,7 +78,7 @@ if ($courseid) {
         }
 
         // Return to previous page
-        $referer = clean_param($_SERVER['HTTP_REFERER'], PARAM_LOCALURL);
+        $referer = get_local_referer(false);
         if (!empty($referer)) {
             redirect($referer);
         } else {
index 5e3d331..dcc4d96 100644 (file)
@@ -29,7 +29,7 @@ $id = required_param('id', PARAM_INT);
 $returnurl = optional_param('returnurl', 0, PARAM_LOCALURL);
 
 if (!isloggedin()) {
-    $referer = clean_param(get_referer(), PARAM_LOCALURL);
+    $referer = get_local_referer();
     if (empty($referer)) {
         // A user that is not logged in has arrived directly on this page,
         // they should be redirected to the course page they are trying to enrol on after logging in.
@@ -108,7 +108,7 @@ if (!$forms) {
     } else if ($returnurl) {
         notice(get_string('notenrollable', 'enrol'), $returnurl);
     } else {
-        $url = clean_param(get_referer(false), PARAM_LOCALURL);
+        $url = get_local_referer(false);
         if (empty($url)) {
             $url = new moodle_url('/index.php');
         }
index 54f0b53..721f141 100644 (file)
@@ -29,7 +29,7 @@
 
     $site = get_site();
     $redirecturl = empty($_SERVER['REDIRECT_URL']) ? '' : $_SERVER['REDIRECT_URL'];
-    $httpreferer = empty($_SERVER['HTTP_REFERER']) ? '' : $_SERVER['HTTP_REFERER'];
+    $httpreferer = get_local_referer(false);
     $requesturi  = empty($_SERVER['REQUEST_URI'])  ? '' : $_SERVER['REQUEST_URI'];
 
     header("HTTP/1.0 404 Not Found");
index bd7229d..d298b15 100644 (file)
@@ -111,7 +111,8 @@ M.gradingform_guideeditor.editmode = function(el, editmode) {
             value = M.util.get_string('clicktoedit', 'gradingform_guide')
             taplain.addClass('empty')
         }
-        taplain.one('.textvalue').set('innerHTML', Y.Escape.html(value))
+        // Replace newlines with <br> tags, when displaying in the page.
+        taplain.one('.textvalue').set('innerHTML', Y.Escape.html(value).replace(/(?:\r\n|\r|\n)/g, '<br>'))
         if (tb) {
             tbplain.one('.textvalue').set('innerHTML', Y.Escape.html(tb.get('value')))
         }
index 87e94de..c99219d 100644 (file)
@@ -145,6 +145,10 @@ class gradingform_guide_renderer extends plugin_renderer_base {
                       'name' => '{NAME}[criteria][{CRITERION-id}][descriptionmarkers]'));
             $maxscore   = html_writer::tag('div', s($criterion['maxscore']),
                 array('class'=>'criteriondescriptionscore', 'name' => '{NAME}[criteria][{CRITERION-id}][maxscore]'));
+
+            // Retain newlines as <br> tags when displaying the marking guide.
+            $description = nl2br($description);
+            $descriptionmarkers = nl2br($descriptionmarkers);
         }
 
         if (isset($criterion['error_description'])) {
@@ -290,6 +294,8 @@ class gradingform_guide_renderer extends plugin_renderer_base {
             } else {
                 $description = s($comment['description']);
             }
+            // Retain newlines as <br> tags when displaying 'frequently used comments'.
+            $description = nl2br($description);
         }
         $descriptionclass = 'description';
         if (isset($comment['error_description'])) {
index b7e5729..fb5e988 100644 (file)
@@ -32,7 +32,9 @@ defined('MOODLE_INTERNAL') || die();
 
 $string['clianswerno'] = 'Не';
 $string['cliansweryes'] = 'Да';
+$string['cliincorrectvalueerror'] = 'Грешка, некоректна стойност "{$a->value}" за "{$a->option}"';
 $string['cliincorrectvalueretry'] = 'Неправилна стойност. Моля опитайте отново';
 $string['clitypevalue'] = 'Тип стойност';
+$string['cliyesnoprompt'] = 'Въведете y (означава да) или n (означава не)';
 $string['environmentrequireinstall'] = 'Трябва да бъде инсталиран и разрешен';
 $string['environmentrequireversion'] = 'Необходима е версия {$a->needed} а Вие имате  {$a->current}';
index eea0465..cee0984 100644 (file)
@@ -31,5 +31,5 @@
 defined('MOODLE_INTERNAL') || die();
 
 $string['parentlanguage'] = '';
-$string['thisdirection'] = 'ltr';
+$string['thisdirection'] = 'izq-a-der';
 $string['thislanguage'] = 'Español - México';
index c0bc147..334adff 100644 (file)
@@ -37,7 +37,11 @@ $string['chooselanguagesub'] = 'Изберете јазик САМО за инс
 $string['clialreadyconfigured'] = 'Конфигурациската датотека config.php веќе постои. Ве молиме користете ја admin/cli/install_database.php за да го инсталирате Moodle на овој сајт.';
 $string['clialreadyinstalled'] = 'Конфигурациската датотека config.php веќе постои. Ве молиме користете ја admin/cli/install_database.php за да го надградите Moodle за овој сајт.';
 $string['cliinstallheader'] = 'Програма за инсталирање на Moodle {$a} командна линија';
+$string['databasehost'] = 'Домаќин на базата на податоци';
+$string['databasename'] = 'Име на базата на податоци';
+$string['databasetypehead'] = 'Одбери го двигателот на базата за податоци';
 $string['dataroot'] = 'Директориум';
+$string['datarootpermission'] = 'Дозвола за директориумот за податоци';
 $string['dbprefix'] = 'Префикс на табели';
 $string['dirroot'] = 'Moodle директориум';
 $string['environmenthead'] = 'Ја проверувам околината...';
@@ -61,6 +65,7 @@ $string['memorylimithelp'] = '<p>Прагот на меморијата кај P
     <p><blockquote>php_value memory_limit 40M</blockquote></p>
     <p>Како и да е, на некои компјутери, ова може да ги спречи <b>сите </b> PHP страници да работат (ќе забележите грешки на страниците) па ќе треба да ја преместите датотеката .htaccess.</p></li>
 </ol>';
+$string['pathswrongadmindir'] = 'Администраторскиот директориум не постои';
 $string['phpversion'] = 'Верзија на PHP';
 $string['phpversionhelp'] = '<p>На Moodle му е потребна верзија на PHP, и тоа најмалку 4.1.0. </p>
 <p>Моментално работите на верзијата {$a} </p>
index f4e9312..731a5b0 100644 (file)
@@ -56,6 +56,7 @@ $string['cachedef_langmenu'] = 'List of available languages';
 $string['cachedef_locking'] = 'Locking';
 $string['cachedef_navigation_expandcourse'] = 'Navigation expandable courses';
 $string['cachedef_observers'] = 'Event observers';
+$string['cachedef_plugin_functions'] = 'Plugins available callbacks';
 $string['cachedef_plugin_manager'] = 'Plugin info manager';
 $string['cachedef_questiondata'] = 'Question definitions';
 $string['cachedef_repositories'] = 'Repositories instances data';
index e0f5cd0..78468c7 100644 (file)
@@ -1530,6 +1530,7 @@ $string['resetnotimplemented'] = 'Reset not implemented';
 $string['resetrecordexpired'] = 'The password reset link you used is more than {$a} minutes old and has expired. Please initiate a new password reset.';
 $string['resetstartdate'] = 'Reset start date';
 $string['resetstatus'] = 'Status';
+$string['resettable'] = 'Reset table preferences';
 $string['resettask'] = 'Task';
 $string['resettodefaults'] = 'Reset to defaults';
 $string['resortsubcategoriesby'] = 'Sort subcategories by {$a} ascending';
index 18a8d7f..6cc4e5c 100644 (file)
@@ -29,14 +29,12 @@ define('AJAX_SCRIPT', true);
 require_once(dirname(__FILE__) . '/../../config.php');
 
 // This should be accessed by only valid logged in user.
-if (!isloggedin() or isguestuser()) {
-    die('Invalid access.');
-}
+require_login(null, false);
 
 // This identifies the type of the branch we want to get. Make sure it's SITE_ADMIN.
 $branchtype = required_param('type', PARAM_INT);
 if ($branchtype !== navigation_node::TYPE_SITE_ADMIN) {
-    die('Wrong node type passed.');
+    throw new coding_exception('Incorrect node type passed');
 }
 
 // Start capturing output in case of broken plugins.
index a44c185..5d1874e 100644 (file)
@@ -578,7 +578,7 @@ class block_manager {
         }
 
         $context = $this->page->context;
-        $contexttest = 'bi.parentcontextid = :contextid2';
+        $contexttest = 'bi.parentcontextid IN (:contextid2, :contextid3)';
         $parentcontextparams = array();
         $parentcontextids = $context->get_parent_context_ids();
         if ($parentcontextids) {
@@ -594,12 +594,14 @@ class block_manager {
         $ccselect = ', ' . context_helper::get_preload_record_columns_sql('ctx');
         $ccjoin = "LEFT JOIN {context} ctx ON (ctx.instanceid = bi.id AND ctx.contextlevel = :contextlevel)";
 
+        $systemcontext = context_system::instance();
         $params = array(
             'contextlevel' => CONTEXT_BLOCK,
             'subpage1' => $this->page->subpage,
             'subpage2' => $this->page->subpage,
             'contextid1' => $context->id,
             'contextid2' => $context->id,
+            'contextid3' => $systemcontext->id,
             'pagetype' => $this->page->pagetype,
         );
         if ($this->page->subpage === '') {
index 8dfc9ce..b1a5e16 100644 (file)
@@ -380,11 +380,12 @@ class manager {
             if (is_web_crawler()) {
                 $user = guest_user();
             }
-            if (!empty($CFG->guestloginbutton) and !$user and !empty($_SERVER['HTTP_REFERER'])) {
+            $referer = get_local_referer(false);
+            if (!empty($CFG->guestloginbutton) and !$user and !empty($referer)) {
                 // Automatically log in users coming from search engine results.
-                if (strpos($_SERVER['HTTP_REFERER'], 'google') !== false ) {
+                if (strpos($referer, 'google') !== false ) {
                     $user = guest_user();
-                } else if (strpos($_SERVER['HTTP_REFERER'], 'altavista') !== false ) {
+                } else if (strpos($referer, 'altavista') !== false ) {
                     $user = guest_user();
                 }
             }
index 6701e12..45599cc 100644 (file)
@@ -249,4 +249,15 @@ $definitions = array(
         'simplekeys' => true,
         'simpledata' => true,
     ),
+
+    // Caches plugins existing functions by function name and file.
+    // Set static acceleration size to 5 to load a few functions.
+    'plugin_functions' => array(
+        'mode' => cache_store::MODE_APPLICATION,
+        'simplekeys' => true,
+        'simpledata' => true,
+        'staticacceleration' => true,
+        'staticaccelerationsize' => 5
+    )
+
 );
index 7803aa8..d270a75 100644 (file)
@@ -1140,6 +1140,7 @@ $services = array(
             'core_user_get_users_by_field',
             'core_user_add_user_private_files',
             'mod_assign_view_grading_table',
+            'mod_scorm_view_scorm',
             ),
         'enabled' => 0,
         'restrictedusers' => 0,
index 1971bdc..8c85c9d 100644 (file)
@@ -2359,3 +2359,23 @@ function get_timezone_record($timezonename) {
 }
 
 /* === Apis deprecated since Moodle 3.0 === */
+/**
+ * Returns the URL of the HTTP_REFERER, less the querystring portion if required.
+ *
+ * @deprecated since Moodle 3.0 MDL-49360 - please do not use this function any more.
+ * @todo Remove this function in Moodle 3.2
+ * @param boolean $stripquery if true, also removes the query part of the url.
+ * @return string The resulting referer or empty string.
+ */
+function get_referer($stripquery = true) {
+    debugging('get_referer() is deprecated. Please use get_local_referer() instead.', DEBUG_DEVELOPER);
+    if (isset($_SERVER['HTTP_REFERER'])) {
+        if ($stripquery) {
+            return strip_querystring($_SERVER['HTTP_REFERER']);
+        } else {
+            return $_SERVER['HTTP_REFERER'];
+        }
+    } else {
+        return '';
+    }
+}
\ No newline at end of file
index 9777cdc..3c6e027 100644 (file)
Binary files a/lib/form/yui/build/moodle-form-dateselector/moodle-form-dateselector-debug.js and b/lib/form/yui/build/moodle-form-dateselector/moodle-form-dateselector-debug.js differ
index 555bcf1..2b4e22a 100644 (file)
Binary files a/lib/form/yui/build/moodle-form-dateselector/moodle-form-dateselector-min.js and b/lib/form/yui/build/moodle-form-dateselector/moodle-form-dateselector-min.js differ
index 9777cdc..3c6e027 100644 (file)
Binary files a/lib/form/yui/build/moodle-form-dateselector/moodle-form-dateselector.js and b/lib/form/yui/build/moodle-form-dateselector/moodle-form-dateselector.js differ
index 684f27f..a0b09a4 100644 (file)
@@ -82,7 +82,7 @@ M.form.dateselector = {
             width: "300px",
             showPrevMonth: true,
             showNextMonth: true,
-            firstdayofweek: config.firstdayofweek,
+            firstdayofweek: parseInt(config.firstdayofweek, 10),
             WEEKDAYS_MEDIUM: [
                 config.sun,
                 config.mon,
index 9725e35..34c2829 100644 (file)
@@ -899,7 +899,7 @@ OET;
     }
 
     public function get_rank() {
-        return 50;
+        return 10;
     }
 }
 
@@ -1141,7 +1141,7 @@ OET;
     }
 
     public function get_rank() {
-        return 20;
+        return 50;
     }
 }
 
@@ -1222,7 +1222,7 @@ OET;
     }
 
     public function get_rank() {
-        return 10;
+        return 20;
     }
 }
 
index c33fc8f..72b62b0 100644 (file)
@@ -2482,6 +2482,11 @@ function require_login($courseorid = null, $autologinguest = true, $cm = null, $
         $preventredirect = true;
     }
 
+    if (AJAX_SCRIPT) {
+        // We cannot redirect for AJAX scripts either.
+        $preventredirect = true;
+    }
+
     // Setup global $COURSE, themes, language and locale.
     if (!empty($courseorid)) {
         if (is_object($courseorid)) {
@@ -2521,11 +2526,15 @@ function require_login($courseorid = null, $autologinguest = true, $cm = null, $
     }
 
     // Redirect to the login page if session has expired, only with dbsessions enabled (MDL-35029) to maintain current behaviour.
-    if ((!isloggedin() or isguestuser()) && !empty($SESSION->has_timed_out) && !$preventredirect && !empty($CFG->dbsessions)) {
-        if ($setwantsurltome) {
-            $SESSION->wantsurl = qualified_me();
+    if ((!isloggedin() or isguestuser()) && !empty($SESSION->has_timed_out) && !empty($CFG->dbsessions)) {
+        if ($preventredirect) {
+            throw new require_login_session_timeout_exception();
+        } else {
+            if ($setwantsurltome) {
+                $SESSION->wantsurl = qualified_me();
+            }
+            redirect(get_login_url());
         }
-        redirect(get_login_url());
     }
 
     // If the user is not even logged in yet then make sure they are.
@@ -2549,8 +2558,10 @@ function require_login($courseorid = null, $autologinguest = true, $cm = null, $
             if ($setwantsurltome) {
                 $SESSION->wantsurl = qualified_me();
             }
-            if (!empty($_SERVER['HTTP_REFERER'])) {
-                $SESSION->fromurl  = $_SERVER['HTTP_REFERER'];
+
+            $referer = get_local_referer(false);
+            if (!empty($referer)) {
+                $SESSION->fromurl = $referer;
             }
 
             // Give auth plugins an opportunity to authenticate or redirect to an external login page
@@ -2807,15 +2818,6 @@ function require_login($courseorid = null, $autologinguest = true, $cm = null, $
         }
     }
 
-    // Set the global $COURSE.
-    // TODO MDL-49434: setting current course/cm should be after the check $cm->uservisible .
-    if ($cm) {
-        $PAGE->set_cm($cm, $course);
-        $PAGE->set_pagelayout('incourse');
-    } else if (!empty($courseorid)) {
-        $PAGE->set_course($course);
-    }
-
     // Check visibility of activity to current user; includes visible flag, conditional availability, etc.
     if ($cm && !$cm->uservisible) {
         if ($preventredirect) {
@@ -2829,6 +2831,14 @@ function require_login($courseorid = null, $autologinguest = true, $cm = null, $
         redirect($url, get_string('activityiscurrentlyhidden'));
     }
 
+    // Set the global $COURSE.
+    if ($cm) {
+        $PAGE->set_cm($cm, $course);
+        $PAGE->set_pagelayout('incourse');
+    } else if (!empty($courseorid)) {
+        $PAGE->set_course($course);
+    }
+
     // Finally access granted, update lastaccess times.
     user_accesstime_log($course->id);
 }
@@ -3981,8 +3991,11 @@ function delete_user(stdClass $user) {
     // Force logout - may fail if file based sessions used, sorry.
     \core\session\manager::kill_user_sessions($user->id);
 
+    // Generate username from email address, or a fake email.
+    $delemail = !empty($user->email) ? $user->email : $user->username . '.' . $user->id . '@unknownemail.invalid';
+    $delname = clean_param($delemail . "." . time(), PARAM_USERNAME);
+
     // Workaround for bulk deletes of users with the same email address.
-    $delname = clean_param($user->email . "." . time(), PARAM_USERNAME);
     while ($DB->record_exists('user', array('username' => $delname))) { // No need to use mnethostid here.
         $delname++;
     }
@@ -4841,10 +4854,12 @@ function remove_course_contents($courseid, $showfeedback = true, array $options
 
     // Cleanup the rest of plugins.
     $cleanuplugintypes = array('report', 'coursereport', 'format');
+    $callbacks = get_plugins_with_function('delete_course', 'lib.php');
     foreach ($cleanuplugintypes as $type) {
-        $plugins = get_plugin_list_with_function($type, 'delete_course', 'lib.php');
-        foreach ($plugins as $plugin => $pluginfunction) {
-            $pluginfunction($course->id, $showfeedback);
+        if (!empty($callbacks[$type])) {
+            foreach ($callbacks[$type] as $pluginfunction) {
+                $pluginfunction($course->id, $showfeedback);
+            }
         }
         if ($showfeedback) {
             echo $OUTPUT->notification($strdeleted.get_string('type_'.$type.'_plural', 'plugin'), 'notifysuccess');
@@ -7021,26 +7036,122 @@ function is_valid_plugin_name($name) {
  *      and the function names as values (e.g. 'report_courselist_hook', 'forum_hook').
  */
 function get_plugin_list_with_function($plugintype, $function, $file = 'lib.php') {
+    global $CFG;
+
+    // We don't include here as all plugin types files would be included.
+    $plugins = get_plugins_with_function($function, $file, false);
+
+    if (empty($plugins[$plugintype])) {
+        return array();
+    }
+
+    $allplugins = core_component::get_plugin_list($plugintype);
+
+    // Reformat the array and include the files.
     $pluginfunctions = array();
-    $pluginswithfile = core_component::get_plugin_list_with_file($plugintype, $file, true);
-    foreach ($pluginswithfile as $plugin => $notused) {
-        $fullfunction = $plugintype . '_' . $plugin . '_' . $function;
+    foreach ($plugins[$plugintype] as $pluginname => $functionname) {
 
-        if (function_exists($fullfunction)) {
-            // Function exists with standard name. Store, indexed by frankenstyle name of plugin.
-            $pluginfunctions[$plugintype . '_' . $plugin] = $fullfunction;
+        // Check that it has not been removed and the file is still available.
+        if (!empty($allplugins[$pluginname])) {
 
-        } else if ($plugintype === 'mod') {
-            // For modules, we also allow plugin without full frankenstyle but just starting with the module name.
-            $shortfunction = $plugin . '_' . $function;
-            if (function_exists($shortfunction)) {
-                $pluginfunctions[$plugintype . '_' . $plugin] = $shortfunction;
+            $filepath = $allplugins[$pluginname] . DIRECTORY_SEPARATOR . $file;
+            if (file_exists($filepath)) {
+                include_once($filepath);
+                $pluginfunctions[$plugintype . '_' . $pluginname] = $functionname;
             }
         }
     }
+
     return $pluginfunctions;
 }
 
+/**
+ * Get a list of all the plugins that define a certain API function in a certain file.
+ *
+ * @param string $function the part of the name of the function after the
+ *      frankenstyle prefix. e.g 'hook' if you are looking for functions with
+ *      names like report_courselist_hook.
+ * @param string $file the name of file within the plugin that defines the
+ *      function. Defaults to lib.php.
+ * @param bool $include Whether to include the files that contain the functions or not.
+ * @return array with [plugintype][plugin] = functionname
+ */
+function get_plugins_with_function($function, $file = 'lib.php', $include = true) {
+    global $CFG;
+
+    $cache = \cache::make('core', 'plugin_functions');
+
+    // Including both although I doubt that we will find two functions definitions with the same name.
+    // Clearning the filename as cache_helper::hash_key only allows a-zA-Z0-9_.
+    $key = $function . '_' . clean_param($file, PARAM_ALPHA);
+
+    if ($pluginfunctions = $cache->get($key)) {
+
+        // Checking that the files are still available.
+        foreach ($pluginfunctions as $plugintype => $plugins) {
+
+            $allplugins = \core_component::get_plugin_list($plugintype);
+            foreach ($plugins as $plugin => $fullpath) {
+
+                // Cache might be out of sync with the codebase, skip the plugin if it is not available.
+                if (empty($allplugins[$plugin])) {
+                    unset($pluginfunctions[$plugintype][$plugin]);
+                    continue;
+                }
+
+                $fileexists = file_exists($allplugins[$plugin] . DIRECTORY_SEPARATOR . $file);
+                if ($include && $fileexists) {
+                    // Include the files if it was requested.
+                    include_once($allplugins[$plugin] . DIRECTORY_SEPARATOR . $file);
+                } else if (!$fileexists) {
+                    // If the file is not available any more it should not be returned.
+                    unset($pluginfunctions[$plugintype][$plugin]);
+                }
+            }
+        }
+        return $pluginfunctions;
+    }
+
+    $pluginfunctions = array();
+
+    // To fill the cached. Also, everything should continue working with cache disabled.
+    $plugintypes = \core_component::get_plugin_types();
+    foreach ($plugintypes as $plugintype => $unused) {
+
+        // We need to include files here.
+        $pluginswithfile = \core_component::get_plugin_list_with_file($plugintype, $file, true);
+        foreach ($pluginswithfile as $plugin => $notused) {
+
+            $fullfunction = $plugintype . '_' . $plugin . '_' . $function;
+
+            $pluginfunction = false;
+            if (function_exists($fullfunction)) {
+                // Function exists with standard name. Store, indexed by frankenstyle name of plugin.
+                $pluginfunction = $fullfunction;
+
+            } else if ($plugintype === 'mod') {
+                // For modules, we also allow plugin without full frankenstyle but just starting with the module name.
+                $shortfunction = $plugin . '_' . $function;
+                if (function_exists($shortfunction)) {
+                    $pluginfunction = $shortfunction;
+                }
+            }
+
+            if ($pluginfunction) {
+                if (empty($pluginfunctions[$plugintype])) {
+                    $pluginfunctions[$plugintype] = array();
+                }
+                $pluginfunctions[$plugintype][$plugin] = $pluginfunction;
+            }
+
+        }
+    }
+    $cache->set($key, $pluginfunctions);
+
+    return $pluginfunctions;
+
+}
+
 /**
  * Lists plugin-like directories within specified directory
  *
index 549eb6d..33c2ad5 100644 (file)
@@ -3915,10 +3915,12 @@ class settings_navigation extends navigation_node {
             }
         }
 
-        // Let admin tools hook into course navigation.
-        $tools = get_plugin_list_with_function('tool', 'extend_navigation_course', 'lib.php');
-        foreach ($tools as $toolfunction) {
-            $toolfunction($coursenode, $course, $coursecontext);
+        // Let plugins hook into course navigation.
+        $pluginsfunction = get_plugins_with_function('extend_navigation_course', 'lib.php');
+        foreach ($pluginsfunction as $plugintype => $plugins) {
+            foreach ($plugins as $pluginfunction) {
+                $pluginfunction($coursenode, $course, $coursecontext);
+            }
         }
 
         // Return we are done
@@ -4484,10 +4486,12 @@ class settings_navigation extends navigation_node {
             }
         }
 
-        // Let admin tools hook into user settings navigation.
-        $tools = get_plugin_list_with_function('tool', 'extend_navigation_user_settings', 'lib.php');
-        foreach ($tools as $toolfunction) {
-            $toolfunction($usersetting, $user, $usercontext, $course, $coursecontext);
+        // Let plugins hook into user settings navigation.
+        $pluginsfunction = get_plugins_with_function('extend_navigation_user_settings', 'lib.php');
+        foreach ($pluginsfunction as $plugintype => $plugins) {
+            foreach ($plugins as $pluginfunction) {
+                $pluginfunction($usersetting, $user, $usercontext, $course, $coursecontext);
+            }
         }
 
         return $usersetting;
@@ -4717,10 +4721,12 @@ class settings_navigation extends navigation_node {
             $frontpage->add(get_string('sitelegacyfiles'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/folder', ''));
         }
 
-        // Let admin tools hook into frontpage navigation.
-        $tools = get_plugin_list_with_function('tool', 'extend_navigation_frontpage', 'lib.php');
-        foreach ($tools as $toolfunction) {
-            $toolfunction($frontpage, $course, $coursecontext);
+        // Let plugins hook into frontpage navigation.
+        $pluginsfunction = get_plugins_with_function('extend_navigation_frontpage', 'lib.php');
+        foreach ($pluginsfunction as $plugintype => $plugins) {
+            foreach ($plugins as $pluginfunction) {
+                $pluginfunction($frontpage, $course, $coursecontext);
+            }
         }
 
         return $frontpage;
index e9b9b7e..35e2f34 100644 (file)
@@ -567,8 +567,8 @@ class theme_config {
                 || false !== strpos($uagent, 'Mac')) {
                 // Looks good in Win XP/Mac/Opera 8/9, Mac/Firefox 2, Camino, Safari.
                 // Not broken in Mac/IE 5, Mac/Netscape 7 (?).
-                $this->rarrow = '&#x25B6;';
-                $this->larrow = '&#x25C0;';
+                $this->rarrow = '&#x25B6;&#xFE0E;';
+                $this->larrow = '&#x25C0;&#xFE0E;';
             }
             elseif ((false !== strpos($uagent, 'Konqueror'))
                 || (false !== strpos($uagent, 'Android')))  {
index d1a5d78..64f4621 100644 (file)
@@ -1560,16 +1560,22 @@ class moodle_page {
             }
         }
 
+        $devicetheme = core_useragent::get_device_type_theme($this->devicetypeinuse);
+
+        // The user is using another device than default, and we have a theme for that, we should use it.
+        $hascustomdevicetheme = core_useragent::DEVICETYPE_DEFAULT != $this->devicetypeinuse && !empty($devicetheme);
+
         foreach ($themeorder as $themetype) {
+
             switch ($themetype) {
                 case 'course':
-                    if (!empty($CFG->allowcoursethemes) && !empty($this->_course->theme) && $this->devicetypeinuse == 'default') {
+                    if (!empty($CFG->allowcoursethemes) && !empty($this->_course->theme) && !$hascustomdevicetheme) {
                         return $this->_course->theme;
                     }
                 break;
 
                 case 'category':
-                    if (!empty($CFG->allowcategorythemes) && $this->devicetypeinuse == 'default') {
+                    if (!empty($CFG->allowcategorythemes) && !$hascustomdevicetheme) {
                         $categories = $this->categories;
                         foreach ($categories as $category) {
                             if (!empty($category->theme)) {
@@ -1586,7 +1592,7 @@ class moodle_page {
                 break;
 
                 case 'user':
-                    if (!empty($CFG->allowuserthemes) && !empty($USER->theme) && $this->devicetypeinuse == 'default') {
+                    if (!empty($CFG->allowuserthemes) && !empty($USER->theme) && !$hascustomdevicetheme) {
                         if ($mnetpeertheme) {
                             return $mnetpeertheme;
                         } else {
@@ -1600,12 +1606,11 @@ class moodle_page {
                         return $mnetpeertheme;
                     }
                     // First try for the device the user is using.
-                    $devicetheme = core_useragent::get_device_type_theme($this->devicetypeinuse);
                     if (!empty($devicetheme)) {
                         return $devicetheme;
                     }
                     // Next try for the default device (as a fallback).
-                    $devicetheme = core_useragent::get_device_type_theme('default');
+                    $devicetheme = core_useragent::get_device_type_theme(core_useragent::DEVICETYPE_DEFAULT);
                     if (!empty($devicetheme)) {
                         return $devicetheme;
                     }
index 45c4712..cf5df35 100644 (file)
@@ -164,6 +164,24 @@ class require_login_exception extends moodle_exception {
     }
 }
 
+/**
+ * Session timeout exception.
+ *
+ * This exception is thrown from require_login()
+ *
+ * @package    core_access
+ * @copyright  2015 Andrew Nicols <andrew@nicols.co.uk>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class require_login_session_timeout_exception extends require_login_exception {
+    /**
+     * Constructor
+     */
+    public function __construct() {
+        moodle_exception::__construct('sessionerroruser', 'error');
+    }
+}
+
 /**
  * Web service parameter exception class
  * @deprecated since Moodle 2.2 - use moodle exception instead
index cbf66ac..39afa90 100644 (file)
@@ -34,6 +34,7 @@ define('TABLE_VAR_SHOW',   3);
 define('TABLE_VAR_IFIRST', 4);
 define('TABLE_VAR_ILAST',  5);
 define('TABLE_VAR_PAGE',   6);
+define('TABLE_VAR_RESET',  7);
 /**#@-*/
 
 /**#@+
@@ -138,6 +139,7 @@ class flexible_table {
             TABLE_VAR_IFIRST => 'tifirst',
             TABLE_VAR_ILAST  => 'tilast',
             TABLE_VAR_PAGE   => 'page',
+            TABLE_VAR_RESET  => 'treset'
         );
     }
 
@@ -508,6 +510,17 @@ class flexible_table {
             $this->prefs['i_first'] = $ifirst;
         }
 
+        // Allow user to reset table preferences.
+        if (optional_param($this->request[TABLE_VAR_RESET], 0, PARAM_BOOL) === 1) {
+            $this->prefs = array(
+                'collapse' => array(),
+                'sortby'   => array(),
+                'i_first'  => '',
+                'i_last'   => '',
+                'textsort' => $this->column_textsort,
+            );
+        }
+
         // Save user preferences if they have changed.
         if ($this->prefs != $oldprefs) {
             if ($this->persistent) {
@@ -971,6 +984,10 @@ class flexible_table {
      */
     function print_nothing_to_display() {
         global $OUTPUT;
+
+        // Render button to allow user to reset table preferences.
+        echo $this->render_reset_button();
+
         $this->print_initials_bar();
 
         echo $OUTPUT->heading(get_string('nothingtodisplay'));
@@ -1325,6 +1342,10 @@ class flexible_table {
      */
     function start_html() {
         global $OUTPUT;
+
+        // Render button to allow user to reset table preferences.
+        echo $this->render_reset_button();
+
         // Do we need to print initial bars?
         $this->print_initials_bar();
 
@@ -1363,6 +1384,21 @@ class flexible_table {
         }
         return $string;
     }
+
+    /**
+     * Generate the HTML for the table preferences reset button.
+     *
+     * @return string HTML fragment.
+     */
+    private function render_reset_button() {
+        $url = $this->baseurl->out(false, array($this->request[TABLE_VAR_RESET] => 1));
+
+        $html  = html_writer::start_div('mdl-right');
+        $html .= html_writer::link($url, get_string('resettable'));
+        $html .= html_writer::end_div();
+
+        return $html;
+    }
 }
 
 
index f12f616..44ccaba 100644 (file)
@@ -792,12 +792,19 @@ EOD;
             $record['userid'] = $USER->id;
         }
 
-        if (!isset($record['name'])) {
-            $record['name'] = 'Tag name ' . $i;
+        if (!isset($record['rawname'])) {
+            if (isset($record['name'])) {
+                $record['rawname'] = $record['name'];
+            } else {
+                $record['rawname'] = 'Tag name ' . $i;
+            }
         }
 
-        if (!isset($record['rawname'])) {
-            $record['rawname'] = 'Raw tag name ' . $i;
+        // Attribute 'name' should be a lowercase version of 'rawname', if not set.
+        if (!isset($record['name'])) {
+            $record['name'] = core_text::strtolower($record['rawname']);
+        } else {
+            $record['name'] = core_text::strtolower($record['name']);
         }
 
         if (!isset($record['tagtype'])) {
index a2ce75f..b2fa587 100644 (file)
@@ -166,6 +166,10 @@ class behat_data_generators extends behat_base {
             'required' => array('qtype', 'questioncategory', 'name'),
             'switchids' => array('questioncategory' => 'category', 'user' => 'createdby')
         ),
+        'tags' => array(
+            'datagenerator' => 'tag',
+            'required' => array('name')
+        ),
     );
 
     /**
index ed19db7..7129e80 100644 (file)
@@ -168,6 +168,13 @@ class core_medialib_testcase extends advanced_testcase {
         $CFG->core_media_enable_mp3 = true;
         $renderer = new core_media_renderer_test($PAGE, '');
         $this->assertSame('mp3, html5audio, link', $renderer->get_players_test());
+
+        // Test QT and HTML5 media order.
+        $CFG->core_media_enable_mp3 = false;
+        $CFG->core_media_enable_html5video = true;
+        $CFG->core_media_enable_qt = true;
+        $renderer = new core_media_renderer_test($PAGE, '');
+        $this->assertSame('html5video, html5audio, qt, link', $renderer->get_players_test());
     }
 
     /**
@@ -210,6 +217,13 @@ class core_medialib_testcase extends advanced_testcase {
     public function test_embed_url_fallbacks() {
         global $CFG, $PAGE;
 
+        // Key strings in the embed code that identify with the media formats being tested.
+        $qt = 'qtplugin.cab';
+        $html5video = '</video>';
+        $html5audio = '</audio>';
+        $link = 'mediafallbacklink';
+        $mp3 = 'mediaplugin_mp3';
+
         $url = new moodle_url('http://example.org/test.mp4');
 
         // All plugins disabled, NOLINK option.
@@ -222,59 +236,58 @@ class core_medialib_testcase extends advanced_testcase {
         // All plugins disabled but not NOLINK.
         $renderer = new core_media_renderer_test($PAGE, '');
         $t = $renderer->embed_url($url);
-        $this->assert_contents(false, false, true, $t);
+        $this->assertContains($link, $t);
 
-        // HTML5 plugin enabled.
+        // Enable media players that can play the same media formats. (ie. qt & html5video for mp4 files, etc.)
         $CFG->core_media_enable_html5video = true;
-        $renderer = new core_media_renderer_test($PAGE, '');
-        $t = $renderer->embed_url($url);
-        $this->assert_contents(false, true, true, $t);
-
-        // QT plugin enabled as well.
+        $CFG->core_media_enable_html5audio = true;
+        $CFG->core_media_enable_mp3 = true;
         $CFG->core_media_enable_qt = true;
-        $renderer = new core_media_renderer_test($PAGE, '');
-        $t = $renderer->embed_url($url);
-        $this->assert_contents(true, true, true, $t);
 
-        // Nolink turned off, plugins still enabled.
-        $t = $renderer->embed_url($url, 0, 0, '',
-                array(core_media::OPTION_NO_LINK => true));
-        $this->assert_contents(true, true, false, $t);
-    }
-
-    /**
-     * Checks the contents of the resulting HTML code to ensure it used the
-     * correct embed method(s).
-     *
-     * @param bool $hasqt True if it should have QT embed code
-     * @param bool $hashtml5 True if it should have HTML5 embed code
-     * @param bool $haslink True if it should have a fallback link
-     * @param string $text HTML content
-     */
-    private function assert_contents($hasqt, $hashtml5, $haslink, $text) {
-        // I tried to avoid making it specific to the exact details of the html
-        // code, picking out only single key strings that would let it check
-        // whether final code contains the right things.
-        $qt = 'qtplugin.cab';
-        $html5 = '</video>';
-        $link = 'mediafallbacklink';
-
-        if ($hasqt) {
-            $this->assertContains($qt, $text);
-        } else {
-            $this->assertNotContains($qt, $text);
-        }
-
-        if ($hashtml5) {
-            $this->assertContains($html5, $text);
-        } else {
-            $this->assertNotContains($html5, $text);
-        }
-
-        if ($haslink) {
-            $this->assertContains($link, $text);
-        } else {
-            $this->assertNotContains($link, $text);
+        // Test media formats that can be played by 2 or more players.
+        $mediaformats = array('mp3', 'm4a', 'mp4', 'm4v');
+
+        foreach ($mediaformats as $format) {
+            $url = new moodle_url('http://example.org/test.' . $format);
+            $renderer = new core_media_renderer_test($PAGE, '');
+            $textwithlink = $renderer->embed_url($url);
+            $textwithoutlink = $renderer->embed_url($url, 0, 0, '', array(core_media::OPTION_NO_LINK => true));
+
+            switch ($format) {
+                case 'mp3':
+                    $this->assertContains($mp3, $textwithlink);
+                    $this->assertContains($html5audio, $textwithlink);
+                    $this->assertContains($link, $textwithlink);
+
+                    $this->assertContains($mp3, $textwithoutlink);
+                    $this->assertContains($html5audio, $textwithoutlink);
+                    $this->assertNotContains($link, $textwithoutlink);
+                    break;
+
+                case 'm4a':
+                    $this->assertContains($qt, $textwithlink);
+                    $this->assertContains($html5audio, $textwithlink);
+                    $this->assertContains($link, $textwithlink);
+
+                    $this->assertContains($qt, $textwithoutlink);
+                    $this->assertContains($html5audio, $textwithoutlink);
+                    $this->assertNotContains($link, $textwithoutlink);
+                    break;
+
+                case 'mp4':
+                case 'm4v':
+                    $this->assertContains($qt, $textwithlink);
+                    $this->assertContains($html5video, $textwithlink);
+                    $this->assertContains($link, $textwithlink);
+
+                    $this->assertContains($qt, $textwithoutlink);
+                    $this->assertContains($html5video, $textwithoutlink);
+                    $this->assertNotContains($link, $textwithoutlink);
+                    break;
+
+                default:
+                    break;
+            }
         }
     }
 
index 3d33588..8ee6dd2 100644 (file)
@@ -1882,6 +1882,10 @@ class core_moodlelib_testcase extends advanced_testcase {
 
         $user = $this->getDataGenerator()->create_user(array('idnumber'=>'abc'));
         $user2 = $this->getDataGenerator()->create_user(array('idnumber'=>'xyz'));
+        $usersharedemail1 = $this->getDataGenerator()->create_user(array('email' => 'sharedemail@example.invalid'));
+        $usersharedemail2 = $this->getDataGenerator()->create_user(array('email' => 'sharedemail@example.invalid'));
+        $useremptyemail1 = $this->getDataGenerator()->create_user(array('email' => ''));
+        $useremptyemail2 = $this->getDataGenerator()->create_user(array('email' => ''));
 
         // Delete user and capture event.
         $sink = $this->redirectEvents();
@@ -1947,6 +1951,30 @@ class core_moodlelib_testcase extends advanced_testcase {
         $result = delete_user($admin);
         $this->assertFalse($result);
 
+        // Simultaneously deleting users with identical email addresses.
+        $result1 = delete_user($usersharedemail1);
+        $result2 = delete_user($usersharedemail2);
+
+        $usersharedemail1after = $DB->get_record('user', array('id' => $usersharedemail1->id));
+        $usersharedemail2after = $DB->get_record('user', array('id' => $usersharedemail2->id));
+        $this->assertTrue($result1);
+        $this->assertTrue($result2);
+        $this->assertStringStartsWith($usersharedemail1->email . '.', $usersharedemail1after->username);
+        $this->assertStringStartsWith($usersharedemail2->email . '.', $usersharedemail2after->username);
+
+        // Simultaneously deleting users without email addresses.
+        $result1 = delete_user($useremptyemail1);
+        $result2 = delete_user($useremptyemail2);
+
+        $useremptyemail1after = $DB->get_record('user', array('id' => $useremptyemail1->id));
+        $useremptyemail2after = $DB->get_record('user', array('id' => $useremptyemail2->id));
+        $this->assertTrue($result1);
+        $this->assertTrue($result2);
+        $this->assertStringStartsWith($useremptyemail1->username . '.' . $useremptyemail1->id . '@unknownemail.invalid.',
+            $useremptyemail1after->username);
+        $this->assertStringStartsWith($useremptyemail2->username . '.' . $useremptyemail2->id . '@unknownemail.invalid.',
+            $useremptyemail2after->username);
+
         $this->resetDebugging();
     }
 
index 347855f..b787dd8 100644 (file)
@@ -3,6 +3,7 @@ information provided here is intended especially for developers.
 
 === 3.0 ===
 
+* get_referer() has been deprecated, please use the get_local_referer function instead.
 * \core\progress\null is renamed to \core\progress\none for improved PHP7 compatibility as null is a reserved word (see MDL-50453).
 * \webservice_xmlrpc_client now respects proxy server settings. If your XMLRPC server is available on your local network and not via your proxy server, you may need to add it to the list of proxy
   server exceptions in $CFG->proxybypass. See MDL-39353 for details.
index 696caa5..bd3689b 100644 (file)
@@ -158,24 +158,6 @@ function strip_querystring($url) {
     }
 }
 
-/**
- * Returns the URL of the HTTP_REFERER, less the querystring portion if required.
- *
- * @param boolean $stripquery if true, also removes the query part of the url.
- * @return string The resulting referer or empty string.
- */
-function get_referer($stripquery=true) {
-    if (isset($_SERVER['HTTP_REFERER'])) {
-        if ($stripquery) {
-            return strip_querystring($_SERVER['HTTP_REFERER']);
-        } else {
-            return $_SERVER['HTTP_REFERER'];
-        }
-    } else {
-        return '';
-    }
-}
-
 /**
  * Returns the name of the current script, WITH the querystring portion.
  *
@@ -234,6 +216,25 @@ function is_https() {
     return (strpos($CFG->httpswwwroot, 'https://') === 0);
 }
 
+/**
+ * Returns the cleaned local URL of the HTTP_REFERER less the URL query string parameters if required.
+ *
+ * @param bool $stripquery if true, also removes the query part of the url.
+ * @return string The resulting referer or empty string.
+ */
+function get_local_referer($stripquery = true) {
+    if (isset($_SERVER['HTTP_REFERER'])) {
+        $referer = clean_param($_SERVER['HTTP_REFERER'], PARAM_LOCALURL);
+        if ($stripquery) {
+            return strip_querystring($referer);
+        } else {
+            return $referer;
+        }
+    } else {
+        return '';
+    }
+}
+
 /**
  * Class for creating and manipulating urls.
  *
index 51adebc..47fc0f6 100644 (file)
Binary files a/lib/yui/build/moodle-core-dragdrop/moodle-core-dragdrop-debug.js and b/lib/yui/build/moodle-core-dragdrop/moodle-core-dragdrop-debug.js differ
index bb3367a..27dec90 100644 (file)
Binary files a/lib/yui/build/moodle-core-dragdrop/moodle-core-dragdrop-min.js and b/lib/yui/build/moodle-core-dragdrop/moodle-core-dragdrop-min.js differ
index 51adebc..47fc0f6 100644 (file)
Binary files a/lib/yui/build/moodle-core-dragdrop/moodle-core-dragdrop.js and b/lib/yui/build/moodle-core-dragdrop/moodle-core-dragdrop.js differ
index 3a4bea6..e7700d0 100644 (file)
@@ -207,6 +207,8 @@ Y.extend(DRAGDROP, Y.Base, {
         if (!this.in_group(drag)) {
             return;
         }
+        // Store the nodes current style, so we can restore it later.
+        this.originalstyle = drag.get('node').getAttribute('style');
         // Set some general styles here
         drag.get('node').setStyle('opacity', '.25');
         drag.get('dragNode').setStyles({
@@ -225,10 +227,7 @@ Y.extend(DRAGDROP, Y.Base, {
             return;
         }
         //Put our general styles back
-        drag.get('node').setStyles({
-            visibility: '',
-            opacity: '1'
-        });
+        drag.get('node').setAttribute('style', this.originalstyle);
         this.drag_end(e);
     },
 
index f610ce8..287d3da 100644 (file)
@@ -258,15 +258,16 @@ if ($session_has_timed_out and !data_submitted()) {
 /// First, let's remember where the user was trying to get to before they got here
 
 if (empty($SESSION->wantsurl)) {
-    $SESSION->wantsurl = (array_key_exists('HTTP_REFERER',$_SERVER) &&
-                          $_SERVER["HTTP_REFERER"] != $CFG->wwwroot &&
-                          $_SERVER["HTTP_REFERER"] != $CFG->wwwroot.'/' &&
-                          $_SERVER["HTTP_REFERER"] != $CFG->httpswwwroot.'/login/' &&
-                          strpos($_SERVER["HTTP_REFERER"], $CFG->httpswwwroot.'/login/?') !== 0 &&
-                          strpos($_SERVER["HTTP_REFERER"], $CFG->httpswwwroot.'/login/index.php') !== 0 &&
-                          clean_param($_SERVER['HTTP_REFERER'], PARAM_LOCALURL) != '')
-                          // There might be some extra params such as ?lang=.
-        ? $_SERVER["HTTP_REFERER"] : NULL;
+    $SESSION->wantsurl = null;
+    $referer = get_local_referer(false);
+    if ($referer &&
+            $referer != $CFG->wwwroot &&
+            $referer != $CFG->wwwroot . '/' &&
+            $referer != $CFG->httpswwwroot . '/login/' &&
+            strpos($referer, $CFG->httpswwwroot . '/login/?') !== 0 &&
+            strpos($referer, $CFG->httpswwwroot . '/login/index.php') !== 0) { // There might be some extra params such as ?lang=.
+        $SESSION->wantsurl = $referer;
+    }
 }
 
 /// Redirect to alternative login URL if needed
index b5c2757..412f72f 100644 (file)
@@ -144,6 +144,7 @@ class mod_assign_mod_form extends moodleform_mod {
             'preventsubmissionnotingroup',
             'assign');
         $mform->setType('preventsubmissionnotingroup', PARAM_BOOL);
+        $mform->disabledIf('preventsubmissionnotingroup', 'teamsubmission', 'eq', 0);
 
         $name = get_string('requireallteammemberssubmit', 'assign');
         $mform->addElement('selectyesno', 'requireallteammemberssubmit', $name);
index d13bd95..4b1b4bc 100644 (file)
@@ -175,6 +175,16 @@ if ($ADMIN->fulltree) {
     $setting->set_locked_flag_options(admin_setting_flag::ENABLED, false);
     $settings->add($setting);
 
+    $name = new lang_string('preventsubmissionnotingroup', 'mod_assign');
+    $description = new lang_string('preventsubmissionnotingroup_help', 'mod_assign');
+    $setting = new admin_setting_configcheckbox('assign/preventsubmissionnotingroup',
+        $name,
+        $description,
+        0);
+    $setting->set_advanced_flag_options(admin_setting_flag::ENABLED, false);
+    $setting->set_locked_flag_options(admin_setting_flag::ENABLED, false);
+    $settings->add($setting);
+
     $name = new lang_string('requireallteammemberssubmit', 'mod_assign');
     $description = new lang_string('requireallteammemberssubmit_help', 'mod_assign');
     $setting = new admin_setting_configcheckbox('assign/requireallteammemberssubmit',
@@ -252,16 +262,6 @@ if ($ADMIN->fulltree) {
     $setting->set_advanced_flag_options(admin_setting_flag::ENABLED, false);
     $setting->set_locked_flag_options(admin_setting_flag::ENABLED, false);
     $settings->add($setting);
-
-    $name = new lang_string('preventsubmissionnotingroup', 'mod_assign');
-    $description = new lang_string('preventsubmissionnotingroup_help', 'mod_assign');
-    $setting = new admin_setting_configcheckbox('assign/preventsubmissionnotingroup',
-                                                    $name,
-                                                    $description,
-                                                    0);
-    $setting->set_advanced_flag_options(admin_setting_flag::ENABLED, false);
-    $setting->set_locked_flag_options(admin_setting_flag::ENABLED, false);
-    $settings->add($setting);
 }
 
 $ADMIN->add('modassignfolder', $settings);
index 4a60767..bbc8e64 100644 (file)
@@ -46,9 +46,6 @@
 }
 
 /* toc style NONE */
-.path-mod-book .book_toc_none {
-  font-size: 0.8em;
-}
 .path-mod-book .book_toc_none ul ul,
 .dir-rtl.path-mod-book .book_toc_none ul ul {
     margin-left: 0;
@@ -56,9 +53,6 @@
 }
 
 /*toc style BULLETS */
-.path-mod-book .book_toc_bullets {
-  font-size: 0.8em;
-}
 .path-mod-book .book_toc_bullets ul ul {
     margin-left: 20px;
 }
     list-style: circle;
 }
 
-/* toc style INDENTED*/
-.path-mod-book .book_toc_indented {
-  font-size: 0.8em;
+.path-mod-book .book_toc_bullets li li:before {
+    display: none;
 }
+
+/* toc style INDENTED*/
 .path-mod-book .book_toc_indented ul {
     margin-left: 5px;
 }
index f8c4a38..500db55 100644 (file)
@@ -178,7 +178,7 @@ if (!$choiceformshown) {
     } else if (!is_enrolled($context)) {
         // Only people enrolled can make a choice
         $SESSION->wantsurl = qualified_me();
-        $SESSION->enrolcancel = clean_param($_SERVER['HTTP_REFERER'], PARAM_LOCALURL);
+        $SESSION->enrolcancel = get_local_referer(false);
 
         $coursecontext = context_course::instance($course->id);
         $courseshortname = format_string($course->shortname, true, array('context' => $coursecontext));
index 0953d99..0742c85 100644 (file)
@@ -3930,7 +3930,7 @@ function forum_set_return() {
     global $CFG, $SESSION;
 
     if (! isset($SESSION->fromdiscussion)) {
-        $referer = clean_param($_SERVER['HTTP_REFERER'], PARAM_LOCALURL);
+        $referer = get_local_referer(false);
         // If the referer is NOT a login screen then save it.
         if (! strncasecmp("$CFG->wwwroot/login", $referer, 300)) {
             $SESSION->fromdiscussion = $referer;
index e6f2b33..1706c6a 100644 (file)
@@ -98,7 +98,7 @@ if ($mark == 'read') {
 //        if (forum_tp_start_tracking($forum->id, $user->id)) {
 //            redirect($returnto, get_string("nowtracking", "forum", $info), 1);
 //        } else {
-//            print_error("Could not start tracking that forum", $_SERVER["HTTP_REFERER"]);
+//            print_error("Could not start tracking that forum", get_local_referer());
 //        }
 }
 
index 2153cf6..bd6bc18 100644 (file)
@@ -53,7 +53,7 @@ $sitecontext = context_system::instance();
 
 if (!isloggedin() or isguestuser()) {
 
-    if (!isloggedin() and !get_referer()) {
+    if (!isloggedin() and !get_local_referer()) {
         // No referer+not logged in - probably coming in via email  See MDL-9052
         require_login();
     }
@@ -87,7 +87,7 @@ if (!isloggedin() or isguestuser()) {
     $PAGE->set_context($modcontext);
     $PAGE->set_title($course->shortname);
     $PAGE->set_heading($course->fullname);
-    $referer = clean_param(get_referer(false), PARAM_LOCALURL);
+    $referer = get_local_referer(false);
 
     echo $OUTPUT->header();
     echo $OUTPUT->confirm(get_string('noguestpost', 'forum').'<br /><br />'.get_string('liketologin'), get_login_url(), $referer);
@@ -117,7 +117,7 @@ if (!empty($forum)) {      // User is starting a new discussion in a forum
             if (!is_enrolled($coursecontext)) {
                 if (enrol_selfenrol_available($course->id)) {
                     $SESSION->wantsurl = qualified_me();
-                    $SESSION->enrolcancel = clean_param($_SERVER['HTTP_REFERER'], PARAM_LOCALURL);
+                    $SESSION->enrolcancel = get_local_referer(false);
                     redirect(new moodle_url('/enrol/index.php', array('id' => $course->id,
                         'returnurl' => '/mod/forum/view.php?f=' . $forum->id)),
                         get_string('youneedtoenrol'));
@@ -131,11 +131,7 @@ if (!empty($forum)) {      // User is starting a new discussion in a forum
         print_error("activityiscurrentlyhidden");
     }
 
-    if (isset($_SERVER["HTTP_REFERER"])) {
-        $SESSION->fromurl = $_SERVER["HTTP_REFERER"];
-    } else {
-        $SESSION->fromurl = '';
-    }
+    $SESSION->fromurl = get_local_referer(false);
 
     // Load up the $post variable.
 
@@ -188,7 +184,7 @@ if (!empty($forum)) {      // User is starting a new discussion in a forum
         if (!isguestuser()) {
             if (!is_enrolled($coursecontext)) {  // User is a guest here!
                 $SESSION->wantsurl = qualified_me();
-                $SESSION->enrolcancel = clean_param($_SERVER['HTTP_REFERER'], PARAM_LOCALURL);
+                $SESSION->enrolcancel = get_local_referer(false);
                 redirect(new moodle_url('/enrol/index.php', array('id' => $course->id,
                     'returnurl' => '/mod/forum/view.php?f=' . $forum->id)),
                     get_string('youneedtoenrol'));
index 237ee77..07fcdd5 100644 (file)
@@ -48,15 +48,15 @@ class mod_forum_renderer extends plugin_renderer_base {
             if ($prev) {
                 $url = new moodle_url('/mod/forum/discuss.php', array('d' => $prev->id));
                 $html .= html_writer::start_tag('li', array('class' => 'prev-discussion'));
-                $html .= html_writer::link($url, $prev->name,
-                    array('aria-label' => get_string('prevdiscussiona', 'mod_forum', $prev->name)));
+                $html .= html_writer::link($url, format_string($prev->name),
+                    array('aria-label' => get_string('prevdiscussiona', 'mod_forum', format_string($prev->name))));
                 $html .= html_writer::end_tag('li');
             }
             if ($next) {
                 $url = new moodle_url('/mod/forum/discuss.php', array('d' => $next->id));
                 $html .= html_writer::start_tag('li', array('class' => 'next-discussion'));
-                $html .= html_writer::link($url, $next->name,
-                    array('aria-label' => get_string('nextdiscussiona', 'mod_forum', $next->name)));
+                $html .= html_writer::link($url, format_string($next->name),
+                    array('aria-label' => get_string('nextdiscussiona', 'mod_forum', format_string($next->name))));
                 $html .= html_writer::end_tag('li');
             }
             $html .= html_writer::end_tag('ul');
index 004825f..ee062bc 100644 (file)
@@ -66,7 +66,7 @@ if (forum_tp_is_tracked($forum) ) {
         $event->trigger();
         redirect($returnto, get_string("nownottracking", "forum", $info), 1);
     } else {
-        print_error('cannottrack', '', $_SERVER["HTTP_REFERER"]);
+        print_error('cannottrack', '', get_local_referer(false));
     }
 
 } else { // subscribe
@@ -75,7 +75,7 @@ if (forum_tp_is_tracked($forum) ) {
         $event->trigger();
         redirect($returnto, get_string("nowtracking", "forum", $info), 1);
     } else {
-        print_error('cannottrack', '', $_SERVER["HTTP_REFERER"]);
+        print_error('cannottrack', '', get_local_referer(false));
     }
 }
 
index a18010c..7bd8414 100644 (file)
@@ -176,23 +176,23 @@ if ($issubscribed) {
         if (\mod_forum\subscriptions::unsubscribe_user($user->id, $forum, $context, true)) {
             redirect($returnto, get_string("nownotsubscribed", "forum", $info), 1);
         } else {
-            print_error('cannotunsubscribe', 'forum', $_SERVER["HTTP_REFERER"]);
+            print_error('cannotunsubscribe', 'forum', get_local_referer(false));
         }
     } else {
         if (\mod_forum\subscriptions::unsubscribe_user_from_discussion($user->id, $discussion, $context)) {
             $info->discussion = $discussion->name;
             redirect($returnto, get_string("discussionnownotsubscribed", "forum", $info), 1);
         } else {
-            print_error('cannotunsubscribe', 'forum', $_SERVER["HTTP_REFERER"]);
+            print_error('cannotunsubscribe', 'forum', get_local_referer(false));
         }
     }
 
 } else {  // subscribe
     if (\mod_forum\subscriptions::subscription_disabled($forum) && !has_capability('mod/forum:managesubscriptions', $context)) {
-        print_error('disallowsubscribe', 'forum', $_SERVER["HTTP_REFERER"]);
+        print_error('disallowsubscribe', 'forum', get_local_referer(false));
     }
     if (!has_capability('mod/forum:viewdiscussion', $context)) {
-        print_error('noviewdiscussionspermission', 'forum', $_SERVER["HTTP_REFERER"]);
+        print_error('noviewdiscussionspermission', 'forum', get_local_referer(false));
     }
     if (is_null($sesskey)) {
         // We came here via link in email.
index a616df1..a9aee92 100644 (file)
@@ -136,14 +136,6 @@ if ( $show ) {
     $hook = $show;
     $show = '';
 }
-/// Processing standard security processes
-if ($course->id != SITEID) {
-    require_login($course);
-}
-if (!$cm->visible and !has_capability('moodle/course:viewhiddenactivities', $context)) {
-    echo $OUTPUT->header();
-    notice(get_string("activityiscurrentlyhidden"));
-}
 
 /// stablishing flag variables
 if ( $sortorder = strtolower($sortorder) ) {
index efbe1a3..7385e88 100644 (file)
@@ -141,15 +141,15 @@ class mod_lesson_mod_form extends moodleform_mod {
         if ($mods = get_course_mods($COURSE->id)) {
             $modinstances = array();
             foreach ($mods as $mod) {
-
-                // get the module name and then store it in a new array
+                // Get the module name and then store it in a new array.
                 if ($module = get_coursemodule_from_instance($mod->modname, $mod->instance, $COURSE->id)) {
-                    if (isset($this->_cm->id) and $this->_cm->id != $mod->id){
+                    // Exclude this lesson, if it's already been saved.
+                    if (!isset($this->_cm->id) || $this->_cm->id != $mod->id) {
                         $modinstances[$mod->id] = $mod->modname.' - '.$module->name;
                     }
                 }
             }
-            asort($modinstances); // sort by module name
+            asort($modinstances); // Sort by module name.
             $modinstances=array(0=>get_string('none'))+$modinstances;
 
             $mform->addElement('select', 'activitylink', get_string('activitylink', 'lesson'), $modinstances);
index 9772d45..b5173a2 100644 (file)
@@ -850,7 +850,7 @@ class mod_quiz_renderer extends plugin_renderer_base {
         $output .= $this->view_information($quiz, $cm, $context, $messages);
         $guestno = html_writer::tag('p', get_string('guestsno', 'quiz'));
         $liketologin = html_writer::tag('p', get_string('liketologin'));
-        $referer = clean_param(get_referer(false), PARAM_LOCALURL);
+        $referer = get_local_referer(false);
         $output .= $this->confirm($guestno."\n\n".$liketologin."\n", get_login_url(), $referer);
         return $output;
     }
index a409959..bc55f03 100644 (file)
@@ -89,7 +89,7 @@ if ($displaytype == RESOURCELIB_DISPLAY_OPEN || $displaytype == RESOURCELIB_DISP
     // For 'open' and 'download' links, we always redirect to the content - except
     // if the user just chose 'save and display' from the form then that would be
     // confusing
-    if (!isset($_SERVER['HTTP_REFERER']) || strpos($_SERVER['HTTP_REFERER'], 'modedit.php') === false) {
+    if (strpos(get_local_referer(false), 'modedit.php') === false) {
         $redirect = true;
     }
 }
diff --git a/mod/scorm/classes/external.php b/mod/scorm/classes/external.php
new file mode 100644 (file)
index 0000000..e6425b4
--- /dev/null
@@ -0,0 +1,105 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * SCORM module external API
+ *
+ * @package    mod_scorm
+ * @category   external
+ * @copyright  2015 Juan Leyva <juan@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since      Moodle 3.0
+ */
+
+defined('MOODLE_INTERNAL') || die;
+
+require_once($CFG->libdir . '/externallib.php');
+require_once($CFG->dirroot . '/mod/scorm/lib.php');
+
+/**
+ * SCORM module external functions
+ *
+ * @package    mod_scorm
+ * @category   external
+ * @copyright  2015 Juan Leyva <juan@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since      Moodle 3.0
+ */
+class mod_scorm_external extends external_api {
+
+    /**
+     * Returns description of method parameters
+     *
+     * @return external_function_parameters
+     * @since Moodle 3.0
+     */
+    public static function view_scorm_parameters() {
+        return new external_function_parameters(
+            array(
+                'scormid' => new external_value(PARAM_INT, 'scorm instance id')
+            )
+        );
+    }
+
+    /**
+     * Trigger the course module viewed event.
+     *
+     * @param int $scormid the scorm instance id
+     * @return array of warnings and status result
+     * @since Moodle 3.0
+     * @throws moodle_exception
+     */
+    public static function view_scorm($scormid) {
+        global $DB, $CFG;
+        require_once($CFG->dirroot . '/mod/scorm/lib.php');
+
+        $params = self::validate_parameters(self::view_scorm_parameters(),
+                                            array(
+                                                'scormid' => $scormid
+                                            ));
+        $warnings = array();
+
+        // Request and permission validation.
+        $scorm = $DB->get_record('scorm', array('id' => $params['scormid']), '*', MUST_EXIST);
+        list($course, $cm) = get_course_and_cm_from_instance($scorm, 'scorm');
+
+        $context = context_module::instance($cm->id);
+        self::validate_context($context);
+
+        // Call the scorm/lib API.
+        scorm_view($scorm, $course, $cm, $context);
+
+        $result = array();
+        $result['status'] = true;
+        $result['warnings'] = $warnings;
+        return $result;
+    }
+
+    /**
+     * Returns description of method result value
+     *
+     * @return external_description
+     * @since Moodle 3.0
+     */
+    public static function view_scorm_returns() {
+        return new external_single_structure(
+            array(
+                'status' => new external_value(PARAM_BOOL, 'status: true if success'),
+                'warnings' => new external_warnings()
+            )
+        );
+    }
+}
diff --git a/mod/scorm/db/services.php b/mod/scorm/db/services.php
new file mode 100644 (file)
index 0000000..1a95c3e
--- /dev/null
@@ -0,0 +1,36 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * SCORM external functions and service definitions.
+ *
+ * @package    mod_scorm
+ * @category   external
+ * @copyright  2015 Juan Leyva <juan@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since      Moodle 3.0
+ */
+
+$functions = array(
+
+    'mod_scorm_view_scorm' => array(
+        'classname'     => 'mod_scorm_external',
+        'methodname'    => 'view_scorm',
+        'description'   => 'Trigger the course module viewed event.',
+        'type'          => 'write',
+        'capabilities'  => ''
+    ),
+);
index bb5bfc2..1e2164f 100644 (file)
@@ -1440,3 +1440,27 @@ function scorm_check_mode($scorm, &$newattempt, &$attempt, $userid, &$mode) {
         }
     }
 }
+
+/**
+ * Trigger the course_module_viewed event.
+ *
+ * @param  stdClass $scorm        scorm object
+ * @param  stdClass $course     course object
+ * @param  stdClass $cm         course module object
+ * @param  stdClass $context    context object
+ * @since Moodle 3.0
+ */
+function scorm_view($scorm, $course, $cm, $context) {
+
+    // Trigger course_module_viewed event.
+    $params = array(
+        'context' => $context,
+        'objectid' => $scorm->id
+    );
+
+    $event = \mod_scorm\event\course_module_viewed::create($params);
+    $event->add_record_snapshot('course_modules', $cm);
+    $event->add_record_snapshot('course', $course);
+    $event->add_record_snapshot('scorm', $scorm);
+    $event->trigger();
+}
index d76312f..7472137 100644 (file)
@@ -840,7 +840,15 @@ function scorm_get_all_attempts($scormid, $userid) {
     return $attemptids;
 }
 
-function scorm_view_display ($user, $scorm, $action, $cm) {
+/**
+ * Displays the entry form and toc if required.
+ *
+ * @param  stdClass $user   user object
+ * @param  stdClass $scorm  scorm object
+ * @param  string   $action base URL for the organizations select box
+ * @param  stdClass $cm     course module object
+ */
+function scorm_print_launch ($user, $scorm, $action, $cm) {
     global $CFG, $DB, $PAGE, $OUTPUT, $COURSE;
 
     if ($scorm->updatefreq == SCORM_UPDATE_EVERYTIME) {
diff --git a/mod/scorm/tests/externallib_test.php b/mod/scorm/tests/externallib_test.php
new file mode 100644 (file)
index 0000000..176fee5
--- /dev/null
@@ -0,0 +1,100 @@
+<?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/>.
+
+/**
+ * SCORM module external functions tests
+ *
+ * @package    mod_scorm
+ * @category   external
+ * @copyright  2015 Juan Leyva <juan@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since      Moodle 3.0
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+
+require_once($CFG->dirroot . '/webservice/tests/helpers.php');
+require_once($CFG->dirroot . '/mod/scorm/lib.php');
+
+/**
+ * SCORM module external functions tests
+ *
+ * @package    mod_scorm
+ * @category   external
+ * @copyright  2015 Juan Leyva <juan@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since      Moodle 3.0
+ */
+class mod_scorm_external_testcase extends externallib_advanced_testcase {
+
+    /**
+     * Test view_scorm
+     */
+    public function test_view_scorm() {
+        global $DB;
+
+        $this->resetAfterTest(true);
+
+        $this->setAdminUser();
+        // Setup test data.
+        $course = $this->getDataGenerator()->create_course();
+        $scorm = $this->getDataGenerator()->create_module('scorm', array('course' => $course->id));
+        $context = context_module::instance($scorm->cmid);
+        $cm = get_coursemodule_from_instance('scorm', $scorm->id);
+
+        // Test invalid instance id.
+        try {
+            mod_scorm_external::view_scorm(0);
+            $this->fail('Exception expected due to invalid mod_scorm instance id.');
+        } catch (moodle_exception $e) {
+            $this->assertEquals('invalidrecord', $e->errorcode);
+        }
+
+        // Test not-enrolled user.
+        $user = self::getDataGenerator()->create_user();
+        $this->setUser($user);
+        try {
+            mod_scorm_external::view_scorm($scorm->id);
+            $this->fail('Exception expected due to not enrolled user.');
+        } catch (moodle_exception $e) {
+            $this->assertEquals('requireloginerror', $e->errorcode);
+        }
+
+        // Test user with full capabilities.
+        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
+        $this->getDataGenerator()->enrol_user($user->id, $course->id, $studentrole->id);
+
+        // Trigger and capture the event.
+        $sink = $this->redirectEvents();
+
+        $result = mod_scorm_external::view_scorm($scorm->id);
+        $result = external_api::clean_returnvalue(mod_scorm_external::view_scorm_returns(), $result);
+
+        $events = $sink->get_events();
+        $this->assertCount(1, $events);
+        $event = array_shift($events);
+
+        // Checking that the event contains the expected values.
+        $this->assertInstanceOf('\mod_scorm\event\course_module_viewed', $event);
+        $this->assertEquals($context, $event->get_context());
+        $moodleurl = new \moodle_url('/mod/scorm/view.php', array('id' => $cm->id));
+        $this->assertEquals($moodleurl, $event->get_url());
+        $this->assertEventContextNotUsed($event);
+        $this->assertNotEmpty($event->get_name());
+    }
+}
diff --git a/mod/scorm/tests/lib_test.php b/mod/scorm/tests/lib_test.php
new file mode 100644 (file)
index 0000000..a0e4a60
--- /dev/null
@@ -0,0 +1,79 @@
+<?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/>.
+
+/**
+ * SCORM module library functions tests
+ *
+ * @package    mod_scorm
+ * @category   test
+ * @copyright  2015 Juan Leyva <juan@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since      Moodle 3.0
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+
+require_once($CFG->dirroot . '/webservice/tests/helpers.php');
+require_once($CFG->dirroot . '/mod/scorm/lib.php');
+
+/**
+ * SCORM module library functions tests
+ *
+ * @package    mod_scorm
+ * @category   test
+ * @copyright  2015 Juan Leyva <juan@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since      Moodle 3.0
+ */
+class mod_scorm_lib_testcase extends externallib_advanced_testcase {
+
+    /**
+     * Test scorm_view
+     * @return void
+     */
+    public function test_scorm_view() {
+        global $CFG;
+
+        $this->resetAfterTest();
+
+        $this->setAdminUser();
+        // Setup test data.
+        $course = $this->getDataGenerator()->create_course();
+        $scorm = $this->getDataGenerator()->create_module('scorm', array('course' => $course->id));
+        $context = context_module::instance($scorm->cmid);
+        $cm = get_coursemodule_from_instance('scorm', $scorm->id);
+
+        // Trigger and capture the event.
+        $sink = $this->redirectEvents();
+
+        scorm_view($scorm, $course, $cm, $context);
+
+        $events = $sink->get_events();
+        $this->assertCount(1, $events);
+        $event = array_shift($events);
+
+        // Checking that the event contains the expected values.
+        $this->assertInstanceOf('\mod_scorm\event\course_module_viewed', $event);
+        $this->assertEquals($context, $event->get_context());
+        $url = new \moodle_url('/mod/scorm/view.php', array('id' => $cm->id));
+        $this->assertEquals($url, $event->get_url());
+        $this->assertEventContextNotUsed($event);
+        $this->assertNotEmpty($event->get_name());
+    }
+
+}
index 13747d8..54f9161 100644 (file)
@@ -24,7 +24,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2015051101;    // The current module version (Date: YYYYMMDDXX).
+$plugin->version   = 2015051102;    // The current module version (Date: YYYYMMDDXX).
 $plugin->requires  = 2015050500;    // Requires this Moodle version.
 $plugin->component = 'mod_scorm';   // Full name of the plugin (used for diagnostics).
 $plugin->cron      = 300;
index f6dc57c..fd98432 100644 (file)
@@ -15,6 +15,7 @@
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
 require_once("../../config.php");
+require_once($CFG->dirroot.'/mod/scorm/lib.php');
 require_once($CFG->dirroot.'/mod/scorm/locallib.php');
 require_once($CFG->dirroot.'/course/lib.php');
 
@@ -123,14 +124,7 @@ $shortname = format_string($course->shortname, true, array('context' => $context
 $pagetitle = strip_tags($shortname.': '.format_string($scorm->name));
 
 // Trigger module viewed event.
-$event = \mod_scorm\event\course_module_viewed::create(array(
-    'objectid' => $scorm->id,
-    'context' => $contextmodule,
-));
-$event->add_record_snapshot('course', $course);
-$event->add_record_snapshot('scorm', $scorm);
-$event->add_record_snapshot('course_modules', $cm);
-$event->trigger();
+scorm_view($scorm, $course, $cm, $contextmodule);
 
 if (empty($preventskip) && empty($launch) && (has_capability('mod/scorm:skipview', $contextmodule))) {
     scorm_simple_play($scorm, $USER, $contextmodule, $cm->id);
@@ -179,7 +173,7 @@ if (!empty($scorm->timeclose) && $timenow > $scorm->timeclose) {
     $scormopen = false;
 }
 if ($scormopen && empty($launch)) {
-    scorm_view_display($USER, $scorm, 'view.php?id='.$cm->id, $cm);
+    scorm_print_launch($USER, $scorm, 'view.php?id='.$cm->id, $cm);
 }
 if (!empty($forcejs)) {
     echo $OUTPUT->box(get_string("forcejavascriptmessage", "scorm"), "generalbox boxaligncenter forcejavascriptmessage");
index 0cf2613..a98013b 100644 (file)
@@ -70,7 +70,7 @@
     echo $OUTPUT->heading($survey->name);
 
     if (survey_already_done($survey->id, $USER->id)) {
-        notice(get_string("alreadysubmitted", "survey"), clean_param($_SERVER["HTTP_REFERER"], PARAM_LOCALURL));
+        notice(get_string("alreadysubmitted", "survey"), get_local_referer(false));
         exit;
     }
 
index bff2c99..2eaa8ae 100644 (file)
@@ -1,6 +1,10 @@
 This files describes API changes in /mod/* - activity modules,
 information provided here is intended especially for developers.
 
+=== 3.0 ===
+
+* Function scorm_view_display was renamed to scorm_print_launch to avoid confussion with new function scorm_view.
+
 === 2.9 ===
 
 * Added Grade to pass field to mod_form for activities that support grading.
index 03ad747..4ca3131 100644 (file)
@@ -68,7 +68,7 @@ $displaytype = url_get_final_display_type($url);
 if ($displaytype == RESOURCELIB_DISPLAY_OPEN) {
     // For 'open' links, we always redirect to the content - except if the user
     // just chose 'save and display' from the form then that would be confusing
-    if (!isset($_SERVER['HTTP_REFERER']) || strpos($_SERVER['HTTP_REFERER'], 'modedit.php') === false) {
+    if (strpos(get_local_referer(false), 'modedit.php') === false) {
         $redirect = true;
     }
 }
index 12f62bc..58b7c09 100644 (file)
@@ -40,7 +40,7 @@ require_once($CFG->dirroot . '/mod/wiki/pagelib.php');
 $pageid = required_param('pageid', PARAM_INT);
 $contentformat = optional_param('contentformat', '', PARAM_ALPHA);
 $option = optional_param('editoption', '', PARAM_TEXT);
-$section = optional_param('section', "", PARAM_TEXT);
+$section = optional_param('section', "", PARAM_RAW);
 $version = optional_param('version', -1, PARAM_INT);
 $attachments = optional_param('attachments', 0, PARAM_INT);
 $deleteuploads = optional_param('deleteuploads', 0, PARAM_RAW);
index d890fe9..be2fd74 100644 (file)
@@ -60,7 +60,7 @@ if (!wiki_user_can_view($subwiki, $wiki)) {
 require_capability('mod/wiki:managefiles', $context);
 
 if (empty($returnurl)) {
-    $referer = clean_param($_SERVER['HTTP_REFERER'], PARAM_LOCALURL);
+    $referer = get_local_referer(false);
     if (!empty($referer)) {
         $returnurl = $referer;
     } else {
index a3cda79..e327aab 100644 (file)
@@ -196,6 +196,7 @@ $string['notassessed'] = 'Not assessed yet';
 $string['notoverridden'] = 'Not overridden';
 $string['noworkshops'] = 'There are no workshops in this course';
 $string['noyoursubmission'] = 'You have not submitted your work yet';
+$string['nothingfound'] = 'Nothing to display';
 $string['nullgrade'] = '-';
 $string['overallfeedback'] = 'Overall feedback';
 $string['overallfeedbackfiles'] = 'Maximum number of overall feedback attachments';
@@ -265,11 +266,14 @@ If you enable this feature, it is recommended to set up the scheduled allocation
 $string['submissiongrade'] = 'Grade for submission';
 $string['submissiongrade_help'] = 'This setting specifies the maximum grade that may be obtained for submitted work.';
 $string['submissiongradeof'] = 'Grade for submission (of {$a})';
+$string['submissionlastmodified'] = 'Last modified';
 $string['submissionsettings'] = 'Submission settings';
 $string['submissionstart'] = 'Open for submissions from';
 $string['submissionstartevent'] = '{$a} (opens for submissions)';
 $string['submissionstartdatetime'] = 'Open for submissions from {$a->daydatetime} ({$a->distanceday})';
 $string['submissiontitle'] = 'Title';
+$string['submissionsreport'] = 'Workshop submissions report';
+$string['submittednotsubmitted'] = 'Submitted ({$a->submitted}) / not submitted ({$a->notsubmitted})';
 $string['subplugintype_workshopallocation'] = 'Submissions allocation method';
 $string['subplugintype_workshopallocation_plural'] = 'Submissions allocation methods';
 $string['subplugintype_workshopeval'] = 'Grading evaluation method';
index 55fee5a..4f98583 100644 (file)
@@ -1782,7 +1782,8 @@ class workshop {
             return array();
         }
 
-        if (!in_array($sortby, array('lastname','firstname','submissiontitle','submissiongrade','gradinggrade'))) {
+        if (!in_array($sortby, array('lastname', 'firstname', 'submissiontitle', 'submissionmodified',
+                'submissiongrade', 'gradinggrade'))) {
             $sortby = 'lastname';
         }
 
@@ -1813,7 +1814,8 @@ class workshop {
             }
             $sqlsort = implode(',', $sqlsort);
             $picturefields = user_picture::fields('u', array(), 'userid');
-            $sql = "SELECT $picturefields, s.title AS submissiontitle, s.grade AS submissiongrade, ag.gradinggrade
+            $sql = "SELECT $picturefields, s.title AS submissiontitle, s.timemodified AS submissionmodified,
+                           s.grade AS submissiongrade, ag.gradinggrade
                       FROM {user} u
                  LEFT JOIN {workshop_submissions} s ON (s.authorid = u.id AND s.workshopid = :workshopid1 AND s.example = 0)
                  LEFT JOIN {workshop_aggregations} ag ON (ag.userid = u.id AND ag.workshopid = :workshopid2)
index e5cc7ac..91ac7fa 100644 (file)
@@ -420,21 +420,29 @@ class mod_workshop_renderer extends plugin_renderer_base {
             $sortbyname = $sortbyfirstname . ' / ' . $sortbylastname;
         }
 
+        $sortbysubmisstiontitle = $this->helper_sortable_heading(get_string('submission', 'workshop'), 'submissiontitle',
+                $options->sortby, $options->sorthow);
+        $sortbysubmisstionlastmodified = $this->helper_sortable_heading(get_string('submissionlastmodified', 'workshop'),
+                'submissionmodified', $options->sortby, $options->sorthow);
+        $sortbysubmisstion = $sortbysubmisstiontitle . ' / ' . $sortbysubmisstionlastmodified;
+
         $table->head = array();
         $table->head[] = $sortbyname;
-        $table->head[] = $this->helper_sortable_heading(get_string('submission', 'workshop'), 'submissiontitle',
-                $options->sortby, $options->sorthow);
-        $table->head[] = $this->helper_sortable_heading(get_string('receivedgrades', 'workshop'));
-        if ($options->showsubmissiongrade) {
-            $table->head[] = $this->helper_sortable_heading(get_string('submissiongradeof', 'workshop', $data->maxgrade),
-                    'submissiongrade', $options->sortby, $options->sorthow);
-        }
-        $table->head[] = $this->helper_sortable_heading(get_string('givengrades', 'workshop'));
-        if ($options->showgradinggrade) {
-            $table->head[] = $this->helper_sortable_heading(get_string('gradinggradeof', 'workshop', $data->maxgradinggrade),
-                    'gradinggrade', $options->sortby, $options->sorthow);
+        $table->head[] = $sortbysubmisstion;
+
+        // If we are in submission phase ignore the following headers (columns).
+        if ($options->workshopphase != workshop::PHASE_SUBMISSION) {
+            $table->head[] = $this->helper_sortable_heading(get_string('receivedgrades', 'workshop'));
+            if ($options->showsubmissiongrade) {
+                $table->head[] = $this->helper_sortable_heading(get_string('submissiongradeof', 'workshop', $data->maxgrade),
+                        'submissiongrade', $options->sortby, $options->sorthow);
+            }
+            $table->head[] = $this->helper_sortable_heading(get_string('givengrades', 'workshop'));
+            if ($options->showgradinggrade) {
+                $table->head[] = $this->helper_sortable_heading(get_string('gradinggradeof', 'workshop', $data->maxgradinggrade),
+                        'gradinggrade', $options->sortby, $options->sorthow);
+            }
         }
-
         $table->rowclasses  = array();
         $table->colclasses  = array();
         $table->data        = array();
@@ -484,6 +492,13 @@ class mod_workshop_renderer extends plugin_renderer_base {
                     $cell->attributes['class'] = 'submission';
                     $row->cells[] = $cell;
                 }
+
+                // If we are in submission phase ignore the following columns.
+                if ($options->workshopphase == workshop::PHASE_SUBMISSION) {
+                    $table->data[] = $row;
+                    continue;
+                }
+
                 // column #3 - received grades
                 if ($tr % $spanreceived == 0) {
                     $idx = intval($tr / $spanreceived);
@@ -997,6 +1012,9 @@ class mod_workshop_renderer extends plugin_renderer_base {
             $url = new moodle_url('/mod/workshop/submission.php',
                                   array('cmid' => $this->page->context->instanceid, 'id' => $participant->submissionid));
             $out = html_writer::link($url, format_string($participant->submissiontitle), array('class'=>'title'));
+
+            $lastmodified = get_string('userdatemodified', 'workshop', userdate($participant->submissionmodified));
+            $out .= html_writer::tag('div', $lastmodified, array('class' => 'lastmodified'));
         }
 
         return $out;
index bca77ad..8a0787a 100644 (file)
     text-align: center;
 }
 
+.path-mod-workshop .lastmodified {
+    line-height: 1.0em;
+}
+
+.path-mod-workshop .nothingfound {
+    font-size: 150%;
+    color: #FF4500;
+}
+
 .path-mod-workshop .workshop-risk-dataloss { vertical-align: text-bottom; }
index 53a8c0a..e8b9957 100644 (file)
@@ -64,10 +64,12 @@ Feature: Workshop submission and assessment
     And I follow "TestWorkshop"
     And I should see "to allocate: 3"
     And I should see "There is at least one author who has not yet submitted their work"
-    And I should see "All submissions (3)"
-    And I should see "Submission1"
-    And I should see "Submission2"
-    And I should see "Submission3"
+    Then I should see "Workshop submissions report"
+    And I should see "Submitted (3) / not submitted (1)"
+    And I should see "Submission1" in the "Sam1 Student1" "table_row"
+    And I should see "Submission2" in the "Sam2 Student2" "table_row"
+    And I should see "Submission3" in the "Sam3 Student3" "table_row"
+    And I should see "No submission found for this user" in the "Sam4 Student4" "table_row"
     And I allocate submissions in workshop "TestWorkshop" as:"
       | Participant   | Reviewer      |
       | Sam1 Student1 | Sam2 Student2 |
index 10a26d8..66adc17 100644 (file)
@@ -224,32 +224,44 @@ case workshop::PHASE_SUBMISSION:
             }
         }
 
-        $countsubmissions = $workshop->count_submissions('all', $groupid);
+        print_collapsible_region_start('', 'workshop-viewlet-allsubmissions', get_string('submissionsreport', 'workshop'));
+
         $perpage = get_user_preferences('workshop_perpage', 10);
-        $pagingbar = new paging_bar($countsubmissions, $page, $perpage, $PAGE->url, 'page');
+        $data = $workshop->prepare_grading_report_data($USER->id, $groupid, $page, $perpage, $sortby, $sorthow);
+        if ($data) {
+            $countparticipants = $workshop->count_participants();
+            $countsubmissions = $workshop->count_submissions(array_keys($data->grades), $groupid);
+            $a = new stdClass();
+            $a->submitted = $countsubmissions;
+            $a->notsubmitted = $data->totalcount - $countsubmissions;
 
-        print_collapsible_region_start('', 'workshop-viewlet-allsubmissions', get_string('allsubmissions', 'workshop', $countsubmissions));
-        echo $output->box_start('generalbox allsubmissions');
-        echo $output->container(groups_print_activity_menu($workshop->cm, $PAGE->url, true), 'groupwidget');
+            echo html_writer::tag('div', get_string('submittednotsubmitted', 'workshop', $a));
 
-        if ($countsubmissions == 0) {
-            echo $output->container(get_string('nosubmissions', 'workshop'), 'nosubmissions');
+            echo $output->container(groups_print_activity_menu($workshop->cm, $PAGE->url, true), 'groupwidget');
+
+            // Prepare the paging bar.
+            $baseurl = new moodle_url($PAGE->url, array('sortby' => $sortby, 'sorthow' => $sorthow));
+            $pagingbar = new paging_bar($data->totalcount, $page, $perpage, $baseurl, 'page');
+
+            // Populate the display options for the submissions report.
+            $reportopts                     = new stdclass();
+            $reportopts->showauthornames     = has_capability('mod/workshop:viewauthornames', $workshop->context);
+            $reportopts->showreviewernames   = has_capability('mod/workshop:viewreviewernames', $workshop->context);
+            $reportopts->sortby              = $sortby;
+            $reportopts->sorthow             = $sorthow;
+            $reportopts->showsubmissiongrade = false;
+            $reportopts->showgradinggrade    = false;
+            $reportopts->workshopphase       = $workshop->phase;
 
-        } else {
-            $submissions = $workshop->get_submissions('all', $groupid, $page * $perpage, $perpage);
-            $shownames = has_capability('mod/workshop:viewauthornames', $workshop->context);
             echo $output->render($pagingbar);
-            foreach ($submissions as $submission) {
-                echo $output->render($workshop->prepare_submission_summary($submission, $shownames));
-            }
+            echo $output->render(new workshop_grading_report($data, $reportopts));
             echo $output->render($pagingbar);
             echo $output->perpage_selector($perpage);
+        } else {
+            echo html_writer::tag('div', get_string('nothingfound', 'workshop'), array('class' => 'nothingfound'));
         }
-
-        echo $output->box_end();
         print_collapsible_region_end();
     }
-
     break;
 
 case workshop::PHASE_ASSESSMENT:
@@ -298,6 +310,7 @@ case workshop::PHASE_ASSESSMENT:
             $reportopts->sorthow                = $sorthow;
             $reportopts->showsubmissiongrade    = false;
             $reportopts->showgradinggrade       = false;
+            $reportopts->workshopphase          = $workshop->phase;
 
             print_collapsible_region_start('', 'workshop-viewlet-gradereport', get_string('gradesreport', 'workshop'));
             echo $output->box_start('generalbox gradesreport');
@@ -447,6 +460,7 @@ case workshop::PHASE_EVALUATION:
             $reportopts->sorthow                = $sorthow;
             $reportopts->showsubmissiongrade    = true;
             $reportopts->showgradinggrade       = true;
+            $reportopts->workshopphase          = $workshop->phase;
 
             print_collapsible_region_start('', 'workshop-viewlet-gradereport', get_string('gradesreport', 'workshop'));
             echo $output->box_start('generalbox gradesreport');
@@ -565,6 +579,7 @@ case workshop::PHASE_CLOSED:
             $reportopts->sorthow                = $sorthow;
             $reportopts->showsubmissiongrade    = true;
             $reportopts->showgradinggrade       = true;
+            $reportopts->workshopphase          = $workshop->phase;
 
             print_collapsible_region_start('', 'workshop-viewlet-gradereport', get_string('gradesreport', 'workshop'));
             echo $output->box_start('generalbox gradesreport');
index bb994c8..8bad3d9 100644 (file)
@@ -1,5 +1,4 @@
 <?php
-
 // This file is part of Moodle - http://moodle.org/
 //
 // Moodle is free software: you can redistribute it and/or modify
@@ -104,11 +103,11 @@ if (!isguestuser()) {   // Skip default home page for guests
 }
 
 // Toggle the editing state and switches
-if ($PAGE->user_allowed_editing()) {
+if (empty($CFG->forcedefaultmymoodle) && $PAGE->user_allowed_editing()) {
     if ($reset !== null) {
         if (!is_null($userid)) {
             require_sesskey();
-            if(!$currentpage = my_reset_page($userid, MY_PAGE_PRIVATE)){
+            if (!$currentpage = my_reset_page($userid, MY_PAGE_PRIVATE)) {
                 print_error('reseterror', 'my');
             }
             redirect(new moodle_url('/my'));
index b93b29e..017abc2 100644 (file)
@@ -58,7 +58,7 @@ function my_get_page($userid, $private=MY_PAGE_PRIVATE) {
 function my_copy_page($userid, $private=MY_PAGE_PRIVATE, $pagetype='my-index') {
     global $DB;
 
-    if ($customised = $DB->record_exists('my_pages', array('userid' => $userid, 'private' => $private))) {
+    if ($customised = $DB->get_record('my_pages', array('userid' => $userid, 'private' => $private))) {
         return $customised;  // We're done!
     }
 
index 704d482..7cb63b7 100644 (file)
@@ -272,20 +272,18 @@ if (!empty($instanceid) && !empty($roleid)) {
 
     // Get record from sql_internal_table_reader and merge with records got from legacy log (if needed).
     if (!$onlyuselegacyreader) {
-        $sql = "SELECT ra.userid, $usernamefields, u.idnumber, COUNT(l.actioncount) AS count
-                  FROM (SELECT DISTINCT userid FROM {role_assignments} WHERE contextid $relatedctxsql AND roleid = :roleid ) ra
-                  JOIN {user} u ON u.id = ra.userid
+        $sql = "SELECT ra.userid, $usernamefields, u.idnumber, COUNT(DISTINCT l.timecreated) AS count
+                  FROM {user} u
+                  JOIN {role_assignments} ra ON u.id = ra.userid AND ra.contextid $relatedctxsql AND ra.roleid = :roleid
              $groupsql
-             LEFT JOIN (
-                    SELECT userid, COUNT(crud) AS actioncount
-                      FROM {" . $logtable . "}
-                     WHERE contextinstanceid = :instanceid
-                       AND timecreated > :timefrom" . $crudsql ."
-                       AND edulevel = :edulevel
-                       AND anonymous = 0
-                       AND contextlevel = :contextlevel
-                       AND (origin = 'web' OR origin = 'ws')
-                  GROUP BY userid,timecreated) l ON (l.userid = ra.userid)";
+                  LEFT JOIN {" . $logtable . "} l
+                     ON l.contextinstanceid = :instanceid
+                       AND l.timecreated > :timefrom" . $crudsql ."
+                       AND l.edulevel = :edulevel
+                       AND l.anonymous = 0
+                       AND l.contextlevel = :contextlevel
+                       AND (l.origin = 'web' OR l.origin = 'ws')
+                       AND l.userid = ra.userid";
         // We add this after the WHERE statement that may come below.
         $groupbysql = " GROUP BY ra.userid, $usernamefields, u.idnumber";
 
index 8f26369..deccb47 100644 (file)
@@ -156,10 +156,10 @@ switch($action) {
 }
 
 if ($err_notice) {
-    echo $OUTPUT->notification($err_notice, 'red');
+    echo $OUTPUT->notification($err_notice, 'notifyproblem');
 }
 if ($notice) {
-    echo $OUTPUT->notification($notice, 'green');
+    echo $OUTPUT->notification($notice, 'notifysuccess');
 }
 
 // small form to add an official tag
index b503392..ba34087 100644 (file)
@@ -1,6 +1,12 @@
 This files describes API changes in /theme/* themes,
 information provided here is intended especially for theme designer.
 
+=== 3.0 ===
+
+* The renderer function core_availability_renderer::multiple_messages was changed to
+  render_core_availability_multiple_messages, requiring a parameter of the new
+  \core_availability\multiple_messages type.
+
 === 2.9 ===
 
 * Themes Bootstrapbase, Clean and More have undergone some changes for RTL layouts see - MDL-48160.
index 05837e8..d306425 100644 (file)
@@ -69,13 +69,13 @@ class manager {
         }
 
         // Plugins.
-        $types = \core_component::get_plugin_types();
-        foreach ($types as $type => $dir) {
-            $pluginlist = get_plugin_list_with_function($type, "myprofile_navigation", "lib.php");
-            foreach ($pluginlist as $function) {
+        $pluginswithfunction = get_plugins_with_function('myprofile_navigation', 'lib.php');
+        foreach ($pluginswithfunction as $plugins) {
+            foreach ($plugins as $function) {
                 $function($tree, $user, $iscurrentuser, $course);
             }
         }
+
         $tree->sort_categories();
         return $tree;
     }
index 80711c7..f2cdcd6 100644 (file)
@@ -112,7 +112,7 @@ if ($currentuser) {
         // Need to have full access to a course to see the rest of own info.
         echo $OUTPUT->header();
         echo $OUTPUT->heading(get_string('notenrolled', '', $fullname));
-        $referer = clean_param($_SERVER['HTTP_REFERER'], PARAM_LOCALURL);
+        $referer = get_local_referer(false);
         if (!empty($referer)) {
             echo $OUTPUT->continue_button($referer);
         }
@@ -144,7 +144,7 @@ if ($currentuser) {
             $PAGE->navbar->add($struser);
             echo $OUTPUT->heading(get_string('notenrolledprofile'));
         }
-        $referer = clean_param($_SERVER['HTTP_REFERER'], PARAM_LOCALURL);
+        $referer = get_local_referer(false);
         if (!empty($referer)) {
             echo $OUTPUT->continue_button($referer);
         }
index 1228df7..ec77c35 100644 (file)
 
 defined('MOODLE_INTERNAL') || die();
 
-$version  = 2015071600.00;              // YYYYMMDD      = weekly release date of this DEV branch.
+$version  = 2015072700.00;              // YYYYMMDD      = weekly release date of this DEV branch.
                                         //         RR    = release increments - 00 in DEV branches.
                                         //           .XX = incremental changes.
 
-$release  = '3.0dev (Build: 20150716)'; // Human-friendly version name
+$release  = '3.0dev (Build: 20150723)'; // Human-friendly version name
 
 $branch   = '30';                       // This version's branch.
 $maturity = MATURITY_ALPHA;             // This version's maturity level.