Merge branch '38663-29' of git://github.com/samhemelryk/moodle
authorDan Poltawski <dan@moodle.com>
Tue, 16 Dec 2014 18:45:33 +0000 (18:45 +0000)
committerDan Poltawski <dan@moodle.com>
Tue, 16 Dec 2014 18:45:33 +0000 (18:45 +0000)
156 files changed:
admin/roles/usersroles.php
admin/tool/health/index.php
admin/tool/health/locallib.php [new file with mode: 0644]
admin/tool/health/tests/healthlib_test.php [new file with mode: 0644]
admin/tool/messageinbound/classes/manager.php
auth/ldap/auth.php
auth/shibboleth/README.txt
auth/shibboleth/logout.php
backup/moodle2/backup_course_task.class.php
backup/moodle2/backup_enrol_plugin.class.php [new file with mode: 0644]
backup/moodle2/backup_plan_builder.class.php
backup/moodle2/backup_stepslib.php
backup/moodle2/restore_enrol_plugin.class.php [new file with mode: 0644]
backup/moodle2/restore_plan_builder.class.php
backup/moodle2/restore_stepslib.php
backup/util/factories/backup_factory.class.php
backup/util/helper/backup_cron_helper.class.php
badges/renderer.php
blocks/calendar_month/block_calendar_month.php
blocks/course_summary/block_course_summary.php
blocks/edit_form.php
blocks/moodleblock.class.php
blocks/rss_client/block_rss_client.php
blocks/rss_client/editfeed.php
blocks/rss_client/lang/en/block_rss_client.php
blocks/tag_flickr/block_tag_flickr.php
blocks/tag_youtube/block_tag_youtube.php
blocks/upgrade.txt
course/editsection.php
course/lib.php
course/modedit.php
course/modlib.php
course/request.php
course/tests/behat/activities_edit_completion.feature [new file with mode: 0644]
course/tests/courselib_test.php
course/tests/externallib_test.php
course/view.php
course/yui/build/moodle-course-toolboxes/moodle-course-toolboxes-debug.js
course/yui/build/moodle-course-toolboxes/moodle-course-toolboxes-min.js
course/yui/build/moodle-course-toolboxes/moodle-course-toolboxes.js
course/yui/src/toolboxes/js/resource.js
enrol/cohort/edit_form.php
enrol/cohort/lib.php
enrol/manual/lib.php
enrol/self/db/access.php
enrol/self/edit_form.php
enrol/self/lang/en/enrol_self.php
enrol/self/locallib.php
enrol/self/version.php
files/renderer.php
grade/edit/tree/lib.php
grade/grading/form/guide/edit.php
grade/report/grader/index.php
grade/report/singleview/classes/local/screen/grade.php
grade/report/singleview/classes/local/screen/screen.php
grade/report/singleview/classes/local/screen/user.php
grade/report/singleview/classes/local/ui/finalgrade.php
grade/report/singleview/index.php
grade/report/singleview/lib.php
grade/report/singleview/tests/behat/singleview.feature
grade/report/singleview/tests/fixtures/screen.php [new file with mode: 0644]
grade/report/singleview/tests/screen_test.php [new file with mode: 0644]
grade/report/upgrade.txt
group/autogroup_form.php
help.php
index.php
install/lang/es/error.php
install/lang/es/install.php
install/lang/mk/admin.php
lang/en/repository.php
lib/behat/classes/behat_selectors.php
lib/behat/classes/util.php
lib/classes/plugininfo/theme.php
lib/classes/session/manager.php
lib/conditionlib.php
lib/coursecatlib.php
lib/deprecatedlib.php
lib/editor/atto/plugins/image/lang/en/atto_image.php
lib/editor/atto/plugins/image/lib.php
lib/editor/atto/plugins/image/version.php
lib/editor/atto/plugins/image/yui/build/moodle-atto_image-button/moodle-atto_image-button-debug.js
lib/editor/atto/plugins/image/yui/build/moodle-atto_image-button/moodle-atto_image-button-min.js
lib/editor/atto/plugins/image/yui/build/moodle-atto_image-button/moodle-atto_image-button.js
lib/editor/atto/plugins/image/yui/src/button/js/button.js
lib/enrollib.php
lib/filelib.php
lib/formslib.php
lib/javascript-static.js
lib/modinfolib.php
lib/moodlelib.php
lib/navigationlib.php
lib/outputcomponents.php
lib/outputrenderers.php
lib/sessionkeepalive_ajax.php [new file with mode: 0644]
lib/tests/accesslib_test.php
lib/tests/behat/behat_data_generators.php
lib/tests/behat/behat_deprecated.php
lib/tests/behat/behat_general.php
lib/tests/behat/behat_hooks.php
lib/tests/conditionlib_test.php [deleted file]
lib/tests/filelib_test.php
lib/tests/modinfolib_test.php
lib/thirdpartylibs.xml
lib/yui/build/moodle-core-checknet/moodle-core-checknet-debug.js
lib/yui/build/moodle-core-checknet/moodle-core-checknet-min.js
lib/yui/build/moodle-core-checknet/moodle-core-checknet.js
lib/yui/src/checknet/js/checknet.js
message/module.js
mod/assign/batchsetmarkingworkflowstateform.php
mod/assign/feedback/editpdf/classes/document_services.php
mod/assign/feedback/editpdf/classes/page_editor.php
mod/assign/feedback/editpdf/tests/behat/group_annotations.feature [new file with mode: 0644]
mod/assign/gradeform.php
mod/assign/lang/en/assign.php
mod/assign/lib.php
mod/assign/locallib.php
mod/assign/tests/behat/quickgrading.feature
mod/assign/tests/locallib_test.php
mod/book/tests/behat/log_entries.feature [new file with mode: 0644]
mod/forum/lib.php
mod/lesson/essay.php
mod/lesson/pagetypes/shortanswer.php
mod/lesson/renderer.php
mod/lesson/styles.css
mod/lesson/tests/behat/import_fillintheblank_question.feature [new file with mode: 0644]
mod/lesson/tests/fixtures/sample_blackboard_fib_qti.dat [new file with mode: 0644]
mod/lesson/view.php
mod/quiz/tests/behat/behat_mod_quiz.php
mod/wiki/tests/behat/wiki_comments.feature
pix/t/viewdetails.png [new file with mode: 0644]
pix/t/viewdetails.svg [new file with mode: 0644]
question/engine/tests/helpers.php
question/tests/behat/copy_questions.feature
question/tests/behat/delete_questions.feature
question/tests/behat/edit_questions.feature
question/tests/behat/preview_question.feature
question/tests/behat/question_categories.feature
question/tests/behat/sort_questions.feature
question/type/match/backup/moodle2/restore_qtype_match_plugin.class.php
report/backups/index.php
report/backups/lang/en/report_backups.php
report/completion/index.php
report/progress/index.php
repository/equella/callback.php
repository/url/lib.php
repository/url/tests/lib_test.php [new file with mode: 0644]
theme/base/style/core.css
theme/base/style/filemanager.css
theme/bootstrapbase/less/moodle/filemanager.less
theme/bootstrapbase/less/moodle/forms.less
theme/bootstrapbase/style/moodle.css
theme/upgrade.txt
user/edit_form.php
user/tests/behat/view_full_profile.feature [new file with mode: 0644]
user/view.php
version.php

index 99ded8f..4a301b1 100644 (file)
@@ -132,7 +132,7 @@ if ($courseid == SITEID) {
     $PAGE->set_heading($course->fullname.': '.$fullname);
 }
 echo $OUTPUT->header();
-echo $OUTPUT->heading($title, 3);
+echo $OUTPUT->heading($title);
 echo $OUTPUT->box_start('generalbox boxaligncenter boxwidthnormal');
 
 // Display them.
index f0fe6c8..1e5b147 100644 (file)
@@ -28,6 +28,7 @@
     $extraws = ob_get_clean();
 
     require_once($CFG->libdir.'/adminlib.php');
+    require_once($CFG->dirroot . '/' . $CFG->admin . '/tool/health/locallib.php');
 
     admin_externalpage_setup('toolhealth');
 
@@ -603,34 +604,10 @@ class problem_000017 extends problem_base {
             $categories = $DB->get_records('question_categories', array(), 'id');
 
             // Look for missing parents.
-            $missingparent = array();
-            foreach ($categories as $category) {
-                if ($category->parent != 0 && !array_key_exists($category->parent, $categories)) {
-                    $missingparent[$category->id] = $category;
-                }
-            }
+            $missingparent = tool_health_category_find_missing_parents($categories);
 
             // Look for loops.
-            $loops = array();
-            while (!empty($categories)) {
-                $current = array_pop($categories);
-                $thisloop = array($current->id => $current);
-                while (true) {
-                    if (isset($thisloop[$current->parent])) {
-                        // Loop detected
-                        $loops[$current->id] = $thisloop;
-                        break;
-                    } else if (!isset($categories[$current->parent])) {
-                        // Got to the top level, or a category we already know is OK.
-                        break;
-                    } else {
-                        // Continue following the path.
-                        $current = $categories[$current->parent];
-                        $thisloop[$current->id] = $current;
-                        unset($categories[$current->id]);
-                    }
-                }
-            }
+            $loops = tool_health_category_find_loops($categories);
 
             $answer = array($missingparent, $loops);
         }
@@ -651,29 +628,126 @@ class problem_000017 extends problem_base {
                 ' structures by the question_categories.parent field. Sometimes ' .
                 ' this tree structure gets messed up.</p>';
 
+        $description .= tool_health_category_list_missing_parents($missingparent);
+        $description .= tool_health_category_list_loops($loops);
+
+        return $description;
+    }
+
+    /**
+     * Outputs resolutions to problems outlined in MDL-34684 with items having themselves as parent
+     *
+     * @link https://tracker.moodle.org/browse/MDL-34684
+     * @return string Formatted html to be output to the browser with instructions and sql statements to run
+     */
+    public function solution() {
+        global $CFG;
+        list($missingparent, $loops) = $this->find_problems();
+
+        $solution = '<p>Consider executing the following SQL queries. These fix ' .
+                'the problem by moving some categories to the top level.</p>';
+
         if (!empty($missingparent)) {
-            $description .= '<p>The following categories are missing their parents:</p><ul>';
-            foreach ($missingparent as $cat) {
-                $description .= "<li>Category $cat->id: " . s($cat->name) . "</li>\n";
-            }
-            $description .= "</ul>\n";
+            $solution .= "<pre>UPDATE " . $CFG->prefix . "question_categories\n" .
+                    "        SET parent = 0\n" .
+                    "        WHERE id IN (" . implode(',', array_keys($missingparent)) . ");</pre>\n";
         }
 
         if (!empty($loops)) {
-            $description .= '<p>The following categories form a loop of parents:</p><ul>';
-            foreach ($loops as $loop) {
-                $description .= "<li><ul>\n";
-                foreach ($loop as $cat) {
-                    $description .= "<li>Category $cat->id: " . s($cat->name) . " has parent $cat->parent</li>\n";
-                }
-                $description .= "</ul></li>\n";
-            }
-            $description .= "</ul>\n";
+            $solution .= "<pre>UPDATE " . $CFG->prefix . "question_categories\n" .
+                    "        SET parent = 0\n" .
+                    "        WHERE id IN (" . implode(',', array_keys($loops)) . ");</pre>\n";
+        }
+
+        return $solution;
+    }
+}
+
+/**
+ * Check course categories tree structure for problems.
+ *
+ * @copyright  2013 Marko Vidberg
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class problem_000018 extends problem_base {
+    /**
+     * Generate title for this problem.
+     *
+     * @return string Title of problem.
+     */
+    public function title() {
+        return 'Course categories tree structure';
+    }
+
+    /**
+     * Search for problems in the course categories.
+     *
+     * @uses $DB
+     * @return array List of categories that contain missing parents or loops.
+     */
+    public function find_problems() {
+        global $DB;
+        static $answer = null;
+
+        if (is_null($answer)) {
+            $categories = $DB->get_records('course_categories', array(), 'id');
+
+            // Look for missing parents.
+            $missingparent = tool_health_category_find_missing_parents($categories);
+
+            // Look for loops.
+            $loops = tool_health_category_find_loops($categories);
+
+            $answer = array($missingparent, $loops);
         }
 
+        return $answer;
+    }
+
+    /**
+     * Check if the problem exists.
+     *
+     * @return boolean True if either missing parents or loops found
+     */
+    public function exists() {
+        list($missingparent, $loops) = $this->find_problems();
+        return !empty($missingparent) || !empty($loops);
+    }
+
+    /**
+     * Set problem severity.
+     *
+     * @return constant Problem severity.
+     */
+    public function severity() {
+        return SEVERITY_SIGNIFICANT;
+    }
+
+    /**
+     * Generate problem description.
+     *
+     * @return string HTML containing details of the problem.
+     */
+    public function description() {
+        list($missingparent, $loops) = $this->find_problems();
+
+        $description = '<p>The course categories should be arranged into tree ' .
+                ' structures by the course_categories.parent field. Sometimes ' .
+                ' this tree structure gets messed up.</p>';
+
+        $description .= tool_health_category_list_missing_parents($missingparent);
+        $description .= tool_health_category_list_loops($loops);
+
         return $description;
     }
-    function solution() {
+
+    /**
+     * Generate solution text.
+     *
+     * @uses $CFG
+     * @return string HTML containing the suggested solution.
+     */
+    public function solution() {
         global $CFG;
         list($missingparent, $loops) = $this->find_problems();
 
@@ -681,14 +755,14 @@ class problem_000017 extends problem_base {
                 'the problem by moving some categories to the top level.</p>';
 
         if (!empty($missingparent)) {
-            $solution .= "<pre>UPDATE " . $CFG->prefix . "question_categories\n" .
-                    "        SET parent = 0\n" .
+            $solution .= "<pre>UPDATE " . $CFG->prefix . "course_categories\n" .
+                    "        SET parent = 0, depth = 1, path = CONCAT('/', id)\n" .
                     "        WHERE id IN (" . implode(',', array_keys($missingparent)) . ");</pre>\n";
         }
 
         if (!empty($loops)) {
-            $solution .= "<pre>UPDATE " . $CFG->prefix . "question_categories\n" .
-                    "        SET parent = 0\n" .
+            $solution .= "<pre>UPDATE " . $CFG->prefix . "course_categories\n" .
+                    "        SET parent = 0, depth = 1, path = CONCAT('/', id)\n" .
                     "        WHERE id IN (" . implode(',', array_keys($loops)) . ");</pre>\n";
         }
 
diff --git a/admin/tool/health/locallib.php b/admin/tool/health/locallib.php
new file mode 100644 (file)
index 0000000..9eb28f9
--- /dev/null
@@ -0,0 +1,128 @@
+<?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/>.
+
+/**
+ * Functions used by the health tool.
+ *
+ * @package    tool_health
+ * @copyright  2013 Marko Vidberg
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Given a list of categories, this function searches for ones
+ * that have a missing parent category.
+ *
+ * @param array $categories List of categories.
+ * @return array List of categories with missing parents.
+ */
+function tool_health_category_find_missing_parents($categories) {
+    $missingparent = array();
+
+    foreach ($categories as $category) {
+        if ($category->parent != 0 && !array_key_exists($category->parent, $categories)) {
+            $missingparent[$category->id] = $category;
+        }
+    }
+
+    return $missingparent;
+}
+
+/**
+ * Generates a list of categories with missing parents.
+ *
+ * @param array $missingparent List of categories with missing parents.
+ * @return string Bullet point list of categories with missing parents.
+ */
+function tool_health_category_list_missing_parents($missingparent) {
+    $description = '';
+
+    if (!empty($missingparent)) {
+        $description .= '<p>The following categories are missing their parents:</p><ul>';
+        foreach ($missingparent as $cat) {
+            $description .= "<li>Category $cat->id: " . s($cat->name) . "</li>\n";
+        }
+        $description .= "</ul>\n";
+    }
+
+    return $description;
+}
+
+/**
+ * Given a list of categories, this function searches for ones
+ * that have loops to previous parent categories.
+ *
+ * @param array $categories List of categories.
+ * @return array List of categories with loops.
+ */
+function tool_health_category_find_loops($categories) {
+    $loops = array();
+
+    while (!empty($categories)) {
+
+        $current = array_pop($categories);
+        $thisloop = array($current->id => $current);
+
+        while (true) {
+            if (isset($thisloop[$current->parent])) {
+                // Loop detected.
+                $loops = $loops + $thisloop;
+                break;
+            } else if ($current->parent === 0) {
+                // Top level.
+                break;
+            } else if (isset($loops[$current->parent])) {
+                // If the parent is in a loop we should also update this category.
+                $loops = $loops + $thisloop;
+                break;
+            } else if (!isset($categories[$current->parent])) {
+                // We already checked this category and is correct.
+                break;
+            } else {
+                // Continue following the path.
+                $current = $categories[$current->parent];
+                $thisloop[$current->id] = $current;
+                unset($categories[$current->id]);
+            }
+        }
+    }
+
+    return $loops;
+}
+
+/**
+ * Generates a list of categories with loops.
+ *
+ * @param array $loops List of categories with loops.
+ * @return string Bullet point list of categories with loops.
+ */
+function tool_health_category_list_loops($loops) {
+    $description = '';
+
+    if (!empty($loops)) {
+        $description .= '<p>The following categories form a loop of parents:</p><ul>';
+        foreach ($loops as $loop) {
+            $description .= "<li>\n";
+            $description .= "Category $loop->id: " . s($loop->name) . " has parent $loop->parent\n";
+            $description .= "</li>\n";
+        }
+        $description .= "</ul>\n";
+    }
+
+    return $description;
+}
diff --git a/admin/tool/health/tests/healthlib_test.php b/admin/tool/health/tests/healthlib_test.php
new file mode 100644 (file)
index 0000000..0190af9
--- /dev/null
@@ -0,0 +1,218 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Unit tests for tool_health.
+ *
+ * @package    tool_health
+ * @copyright  2013 Marko Vidberg
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->dirroot . '/' . $CFG->admin . '/tool/health/locallib.php');
+
+/**
+ * Health lib testcase.
+ *
+ * @package    tool_health
+ * @copyright  2013 Marko Vidberg
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class healthlib_testcase extends advanced_testcase {
+
+    /**
+     * Data provider for test_tool_health_category_find_loops.
+     */
+    public static function provider_loop_categories() {
+        return array(
+            // One item loop including root.
+            0 => array(
+                array(
+                    '1' => (object) array('id' => 1, 'parent' => 1)
+                ),
+                array(
+                    '1' => (object) array('id' => 1, 'parent' => 1)
+                ),
+            ),
+            // One item loop not including root.
+            1 => array(
+                array(
+                    '1' => (object) array('id' => 1, 'parent' => 0),
+                    '2' => (object) array('id' => 2, 'parent' => 2)
+                ),
+                array(
+                    '2' => (object) array('id' => 2, 'parent' => 2)
+                ),
+            ),
+            // Two item loop including root.
+            2 => array(
+                array(
+                    '1' => (object) array('id' => 1, 'parent' => 2),
+                    '2' => (object) array('id' => 2, 'parent' => 1)
+                ),
+                array(
+                    '2' => (object) array('id' => 2, 'parent' => 1),
+                    '1' => (object) array('id' => 1, 'parent' => 2),
+                )
+            ),
+            // Two item loop not including root.
+            3 => array(
+                array(
+                    '1' => (object) array('id' => 1, 'parent' => 0),
+                    '2' => (object) array('id' => 2, 'parent' => 3),
+                    '3' => (object) array('id' => 3, 'parent' => 2),
+                ),
+                array(
+                    '3' => (object) array('id' => 3, 'parent' => 2),
+                    '2' => (object) array('id' => 2, 'parent' => 3),
+                )
+            ),
+            // Three item loop including root.
+            4 => array(
+                array(
+                    '1' => (object) array('id' => 1, 'parent' => 2),
+                    '2' => (object) array('id' => 2, 'parent' => 3),
+                    '3' => (object) array('id' => 3, 'parent' => 1),
+                ),
+                array(
+                    '3' => (object) array('id' => 3, 'parent' => 1),
+                    '1' => (object) array('id' => 1, 'parent' => 2),
+                    '2' => (object) array('id' => 2, 'parent' => 3),
+                )
+            ),
+            // Three item loop not including root.
+            5 => array(
+                array(
+                    '1' => (object) array('id' => 1, 'parent' => 0),
+                    '2' => (object) array('id' => 2, 'parent' => 3),
+                    '3' => (object) array('id' => 3, 'parent' => 4),
+                    '4' => (object) array('id' => 4, 'parent' => 2)
+                ),
+                array(
+                    '4' => (object) array('id' => 4, 'parent' => 2),
+                    '2' => (object) array('id' => 2, 'parent' => 3),
+                    '3' => (object) array('id' => 3, 'parent' => 4),
+                )
+            ),
+            // Multi-loop.
+            6 => array(
+                array(
+                    '1' => (object) array('id' => 1, 'parent' => 2),
+                    '2' => (object) array('id' => 2, 'parent' => 1),
+                    '3' => (object) array('id' => 3, 'parent' => 4),
+                    '4' => (object) array('id' => 4, 'parent' => 5),
+                    '5' => (object) array('id' => 5, 'parent' => 3),
+                    '6' => (object) array('id' => 6, 'parent' => 6),
+                    '7' => (object) array('id' => 7, 'parent' => 1),
+                    '8' => (object) array('id' => 8, 'parent' => 7),
+                ),
+                array(
+                    '1' => (object) array('id' => 1, 'parent' => 2),
+                    '2' => (object) array('id' => 2, 'parent' => 1),
+                    '8' => (object) array('id' => 8, 'parent' => 7),
+                    '7' => (object) array('id' => 7, 'parent' => 1),
+                    '6' => (object) array('id' => 6, 'parent' => 6),
+                    '5' => (object) array('id' => 5, 'parent' => 3),
+                    '3' => (object) array('id' => 3, 'parent' => 4),
+                    '4' => (object) array('id' => 4, 'parent' => 5),
+                )
+            ),
+            // Double-loop
+            7 => array(
+                array(
+                    '1' => (object) array('id' => 1, 'parent' => 2),
+                    '2' => (object) array('id' => 2, 'parent' => 1),
+                    '3' => (object) array('id' => 3, 'parent' => 2),
+                    '4' => (object) array('id' => 4, 'parent' => 2),
+                ),
+                array(
+                    '4' => (object) array('id' => 4, 'parent' => 2),
+                    '3' => (object) array('id' => 3, 'parent' => 2),
+                    '2' => (object) array('id' => 2, 'parent' => 1),
+                    '1' => (object) array('id' => 1, 'parent' => 2),
+                )
+            )
+        );
+    }
+
+    /**
+     * Data provider for test_tool_health_category_find_missing_parents.
+     */
+    public static function provider_missing_parent_categories() {
+        return array(
+           // Test for two items, both with direct ancestor (parent) missing.
+            0 => array(
+                array(
+                    '1' => (object) array('id' => 1, 'parent' => 0),
+                    '2' => (object) array('id' => 2, 'parent' => 3),
+                    '4' => (object) array('id' => 4, 'parent' => 5),
+                    '6' => (object) array('id' => 6, 'parent' => 2)
+                ),
+                array(
+                    '4' => (object) array('id' => 4, 'parent' => 5),
+                    '2' => (object) array('id' => 2, 'parent' => 3)
+                ),
+            )
+        );
+    }
+
+    /**
+     * Test finding loops between two items referring to each other.
+     *
+     * @param array $categories
+     * @param array $expected
+     * @dataProvider provider_loop_categories
+     */
+    public function test_tool_health_category_find_loops($categories, $expected) {
+        $loops = tool_health_category_find_loops($categories);
+        $this->assertEquals($expected, $loops);
+    }
+
+    /**
+     * Test finding missing parent categories.
+     *
+     * @param array $categories
+     * @param array $expected
+     * @dataProvider provider_missing_parent_categories
+     */
+    public function test_tool_health_category_find_missing_parents($categories, $expected) {
+        $missingparent = tool_health_category_find_missing_parents($categories);
+        $this->assertEquals($expected, $missingparent);
+    }
+
+    /**
+     * Test listing missing parent categories.
+     */
+    public function test_tool_health_category_list_missing_parents() {
+        $missingparent = array((object) array('id' => 2, 'parent' => 3, 'name' => 'test'),
+                               (object) array('id' => 4, 'parent' => 5, 'name' => 'test2'));
+        $result = tool_health_category_list_missing_parents($missingparent);
+        $this->assertRegExp('/Category 2: test/', $result);
+        $this->assertRegExp('/Category 4: test2/', $result);
+    }
+
+    /**
+     * Test listing loop categories.
+     */
+    public function test_tool_health_category_list_loops() {
+        $loops = array((object) array('id' => 2, 'parent' => 3, 'name' => 'test'));
+        $result = tool_health_category_list_loops($loops);
+        $this->assertRegExp('/Category 2: test/', $result);
+    }
+}
index dcb4e7b..7e12367 100644 (file)
@@ -295,6 +295,11 @@ class manager {
         // First flag this message to prevent another running hitting this message while we look at the headers.
         $this->add_flag_to_message($messageid, self::MESSAGE_FLAGGED);
 
+        if ($this->is_bulk_message($message, $messageid)) {
+            mtrace("- The message has a bulk header set. This is likely an auto-generated reply - discarding.");
+            return;
+        }
+
         // Record the user that this script is currently being run as.  This is important when re-processing existing
         // messages, as cron_setup_user is called multiple times.
         $originaluser = $USER;
@@ -741,6 +746,43 @@ class manager {
         return in_array($flag, $flags);
     }
 
+    /**
+     * Attempt to determine whether this message is a bulk message (e.g. automated reply).
+     *
+     * @param \Horde_Imap_Client_Data_Fetch $message The message to process
+     * @param string|\Horde_Imap_Client_Ids $messageid The Hore message Uid
+     * @return boolean
+     */
+    private function is_bulk_message(
+            \Horde_Imap_Client_Data_Fetch $message,
+            $messageid) {
+        $query = new \Horde_Imap_Client_Fetch_Query();
+        $query->headerText(array('peek' => true));
+
+        $messagedata = $this->client->fetch($this->get_mailbox(), $query, array('ids' => $messageid))->first();
+
+        // Assume that this message is not bulk to begin with.
+        $isbulk = false;
+
+        // An auto-reply may itself include the Bulk Precedence.
+        $precedence = $messagedata->getHeaderText(0, \Horde_Imap_Client_Data_Fetch::HEADER_PARSE)->getValue('Precedence');
+        $isbulk = $isbulk || strtolower($precedence) == 'bulk';
+
+        // If the X-Autoreply header is set, and not 'no', then this is an automatic reply.
+        $autoreply = $messagedata->getHeaderText(0, \Horde_Imap_Client_Data_Fetch::HEADER_PARSE)->getValue('X-Autoreply');
+        $isbulk = $isbulk || ($autoreply && $autoreply != 'no');
+
+        // If the X-Autorespond header is set, and not 'no', then this is an automatic response.
+        $autorespond = $messagedata->getHeaderText(0, \Horde_Imap_Client_Data_Fetch::HEADER_PARSE)->getValue('X-Autorespond');
+        $isbulk = $isbulk || ($autorespond && $autorespond != 'no');
+
+        // If the Auto-Submitted header is set, and not 'no', then this is a non-human response.
+        $autosubmitted = $messagedata->getHeaderText(0, \Horde_Imap_Client_Data_Fetch::HEADER_PARSE)->getValue('Auto-Submitted');
+        $isbulk = $isbulk || ($autosubmitted && $autosubmitted != 'no');
+
+        return $isbulk;
+    }
+
     /**
      * Send the message to the appropriate handler.
      *
index 03d483b..1da1b66 100644 (file)
@@ -742,7 +742,7 @@ class auth_plugin_ldap extends auth_plugin_base {
                     } while ($entry = ldap_next_entry($ldapconnection, $entry));
                 }
                 unset($ldap_result); // Free mem.
-            } while ($ldap_pagedresults && !empty($ldap_cookie));
+            } while ($ldap_pagedresults && $ldap_cookie !== null && $ldap_cookie != '');
         }
 
         // If LDAP paged results were used, the current connection must be completely
index 849dad2..d1adb68 100644 (file)
@@ -61,8 +61,8 @@ Moodle Configuration with Dual login
 --
 
    Also see:
-   https://spaces.internet2.edu/display/SHIB2/NativeSPRequestMapper and
-   https://spaces.internet2.edu/display/SHIB2/NativeSPAccessControl
+   https://wiki.shibboleth.net/confluence/display/SHIB2/NativeSPRequestMapper and
+   https://wiki.shibboleth.net/confluence/display/SHIB2/NativeSPAccessControl
 
 2. As Moodle admin, go to the 'Administrations >> Users >> Authentication' and
    click on the the 'Shibboleth' settings.
@@ -101,8 +101,8 @@ Moodle Configuration with Dual login
     to the same host. If no SessionInitiator URL is given, the default one
     '/Shibboleth.sso' (only works for Shibboleth 1.3.x) will be used. For
     Shibboleth 2.x you have to add '/Shibboleth.sso/DS' as a SessionInitiator.
-    Also see https://spaces.internet2.edu/display/SHIB/SessionInitiator
-    and https://spaces.internet2.edu/display/SHIB2/NativeSPSessionInitiator
+    Also see https://wiki.shibboleth.net/confluence/display/SHIB/SessionInitiator
+    and https://wiki.shibboleth.net/confluence/display/SHIB2/NativeSPSessionInitiator
 
     Important Note: If you upgraded from a previous version of Moodle and now
                     want to use the integrated WAYF, you have to make sure that
@@ -324,8 +324,8 @@ logout. Hopefully, the Moodle logout helps to motivate the developers to
 implement SLO. On the other hand, the easiest and safest way to log out
 still is to tell users to quit their web browsers :)
 
-Also see https://spaces.internet2.edu/display/SHIB2/SLOIssues and
-https://spaces.internet2.edu/display/SHIB2/NativeSPLogoutInitiator for some
+Also see https://wiki.shibboleth.net/confluence/display/SHIB2/SLOIssues and
+https://wiki.shibboleth.net/confluence/display/SHIB2/NativeSPLogoutInitiator for some
 background information on this topic.
 
 --------------------------------------------------------------------------------
index bd16aaa..d5181fd 100644 (file)
@@ -1,8 +1,8 @@
 <?php
 
 // Implements logout for Shibboleth authenticated users according to:
-// - https://spaces.internet2.edu/display/SHIB2/NativeSPLogoutInitiator
-// - https://spaces.internet2.edu/display/SHIB2/NativeSPNotify
+// - https://wiki.shibboleth.net/confluence/display/SHIB2/NativeSPLogoutInitiator
+// - https://wiki.shibboleth.net/confluence/display/SHIB2/NativeSPNotify
 
 require_once("../../config.php");
 
@@ -66,8 +66,8 @@ Because neither of these two variants seems to be the case, the WSDL file for
 the web service is returned.
 
 For more information see:
-- https://spaces.internet2.edu/display/SHIB2/NativeSPLogoutInitiator
-- https://spaces.internet2.edu/display/SHIB2/NativeSPNotify
+- https://wiki.shibboleth.net/confluence/display/SHIB2/NativeSPLogoutInitiator
+- https://wiki.shibboleth.net/confluence/display/SHIB2/NativeSPNotify
 -->
 
     <types>
index 11f245f..5ed54e9 100644 (file)
@@ -80,6 +80,9 @@ class backup_course_task extends backup_task {
             $this->add_step(new backup_enrolments_structure_step('course_enrolments', 'enrolments.xml'));
         }
 
+        // Annotate enrolment custom fields.
+        $this->add_step(new backup_enrolments_execution_step('annotate_enrol_custom_fields'));
+
         // Annotate all the groups and groupings belonging to the course
         $this->add_step(new backup_annotate_course_groups_and_groupings('annotate_course_groups'));
 
diff --git a/backup/moodle2/backup_enrol_plugin.class.php b/backup/moodle2/backup_enrol_plugin.class.php
new file mode 100644 (file)
index 0000000..a9ede54
--- /dev/null
@@ -0,0 +1,39 @@
+<?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/>.
+
+/**
+ * Defines backup_enrol_plugin class.
+ *
+ * @package     core_backup
+ * @subpackage  moodle2
+ * @category    backup
+ * @copyright   2014 University of Wisconsin
+ * @author      Matt petro
+ * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Base class for enrol backup plugins.
+ *
+ * @package   core_backup
+ * @copyright 2014 University of Wisconsin
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+abstract class backup_enrol_plugin extends backup_plugin {
+    // Use default parent behaviour.
+}
index b78fbad..c359096 100644 (file)
@@ -44,6 +44,7 @@ require_once($CFG->dirroot . '/backup/moodle2/backup_theme_plugin.class.php');
 require_once($CFG->dirroot . '/backup/moodle2/backup_report_plugin.class.php');
 require_once($CFG->dirroot . '/backup/moodle2/backup_coursereport_plugin.class.php');
 require_once($CFG->dirroot . '/backup/moodle2/backup_plagiarism_plugin.class.php');
+require_once($CFG->dirroot . '/backup/moodle2/backup_enrol_plugin.class.php');
 require_once($CFG->dirroot . '/backup/moodle2/backup_subplugin.class.php');
 require_once($CFG->dirroot . '/backup/moodle2/backup_settingslib.php');
 require_once($CFG->dirroot . '/backup/moodle2/backup_stepslib.php');
index de35cc3..7830271 100644 (file)
@@ -555,7 +555,8 @@ class backup_enrolments_structure_step extends backup_structure_step {
 
         $enrol->annotate_ids('role', 'roleid');
 
-        //TODO: let plugins annotate custom fields too and add more children
+        // Add enrol plugin structure.
+        $this->add_plugin_structure('enrol', $enrol, false);
 
         return $enrolments;
     }
@@ -1875,6 +1876,47 @@ class backup_activity_grade_items_to_ids extends backup_execution_step {
     }
 }
 
+
+/**
+ * This step allows enrol plugins to annotate custom fields.
+ *
+ * @package   core_backup
+ * @copyright 2014 University of Wisconsin
+ * @author    Matt Petro
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class backup_enrolments_execution_step extends backup_execution_step {
+
+    /**
+     * Function that will contain all the code to be executed.
+     */
+    protected function define_execution() {
+        global $DB;
+
+        $plugins = enrol_get_plugins(true);
+        $enrols = $DB->get_records('enrol', array(
+                'courseid' => $this->task->get_courseid()));
+
+        // Allow each enrol plugin to add annotations.
+        foreach ($enrols as $enrol) {
+            if (isset($plugins[$enrol->enrol])) {
+                $plugins[$enrol->enrol]->backup_annotate_custom_fields($this, $enrol);
+            }
+        }
+    }
+
+    /**
+     * Annotate a single name/id pair.
+     * This can be called from {@link enrol_plugin::backup_annotate_custom_fields()}.
+     *
+     * @param string $itemname
+     * @param int $itemid
+     */
+    public function annotate_id($itemname, $itemid) {
+        backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), $itemname, $itemid);
+    }
+}
+
 /**
  * This step will annotate all the groups and groupings belonging to the course
  */
diff --git a/backup/moodle2/restore_enrol_plugin.class.php b/backup/moodle2/restore_enrol_plugin.class.php
new file mode 100644 (file)
index 0000000..92d945e
--- /dev/null
@@ -0,0 +1,39 @@
+<?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/>.
+
+/**
+ * Defines restore_enrol_plugin class.
+ *
+ * @package     core_backup
+ * @subpackage  moodle2
+ * @category    backup
+ * @copyright   2014 University of Wisconsin
+ * @author      Matt petro
+ * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Base class for enrol backup plugins.
+ *
+ * @package   core_backup
+ * @copyright 2014 University of Wisconsin
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+abstract class restore_enrol_plugin extends restore_plugin {
+    // Use default parent behaviour.
+}
index b7c65ef..ae48ec9 100644 (file)
@@ -43,6 +43,7 @@ require_once($CFG->dirroot . '/backup/moodle2/restore_report_plugin.class.php');
 require_once($CFG->dirroot . '/backup/moodle2/restore_coursereport_plugin.class.php');
 require_once($CFG->dirroot . '/backup/moodle2/restore_plagiarism_plugin.class.php');
 require_once($CFG->dirroot . '/backup/moodle2/restore_gradingform_plugin.class.php');
+require_once($CFG->dirroot . '/backup/moodle2/restore_enrol_plugin.class.php');
 require_once($CFG->dirroot . '/backup/moodle2/backup_plugin.class.php');
 require_once($CFG->dirroot . '/backup/moodle2/backup_qtype_plugin.class.php');
 require_once($CFG->dirroot . '/backup/moodle2/backup_format_plugin.class.php');
@@ -52,6 +53,7 @@ require_once($CFG->dirroot . '/backup/moodle2/backup_report_plugin.class.php');
 require_once($CFG->dirroot . '/backup/moodle2/backup_coursereport_plugin.class.php');
 require_once($CFG->dirroot . '/backup/moodle2/backup_plagiarism_plugin.class.php');
 require_once($CFG->dirroot . '/backup/moodle2/backup_gradingform_plugin.class.php');
+require_once($CFG->dirroot . '/backup/moodle2/backup_enrol_plugin.class.php');
 require_once($CFG->dirroot . '/backup/moodle2/restore_subplugin.class.php');
 require_once($CFG->dirroot . '/backup/moodle2/restore_settingslib.php');
 require_once($CFG->dirroot . '/backup/moodle2/restore_stepslib.php');
index 7df7a2e..1380f20 100644 (file)
@@ -1884,12 +1884,12 @@ class restore_enrolments_structure_step extends restore_structure_step {
 
     protected function define_structure() {
 
-        $paths = array();
-
-        $paths[] = new restore_path_element('enrol', '/enrolments/enrols/enrol');
-        $paths[] = new restore_path_element('enrolment', '/enrolments/enrols/enrol/user_enrolments/enrolment');
+        $enrol = new restore_path_element('enrol', '/enrolments/enrols/enrol');
+        $enrolment = new restore_path_element('enrolment', '/enrolments/enrols/enrol/user_enrolments/enrolment');
+        // Attach local plugin stucture to enrol element.
+        $this->add_plugin_structure('enrol', $enrol);
 
-        return $paths;
+        return array($enrol, $enrolment);
     }
 
     /**
@@ -2630,7 +2630,21 @@ class restore_course_completion_structure_step extends restore_structure_step {
                 'timecompleted' => $this->apply_date_offset($data->timecompleted),
                 'reaggregate' => $data->reaggregate
             );
-            $DB->insert_record('course_completions', $params);
+
+            $existing = $DB->get_record('course_completions', array(
+                'userid' => $data->userid,
+                'course' => $data->course
+            ));
+
+            // MDL-46651 - If cron writes out a new record before we get to it
+            // then we should replace it with the Truth data from the backup.
+            // This may be obsolete after MDL-48518 is resolved
+            if ($existing) {
+                $params['id'] = $existing->id;
+                $DB->update_record('course_completions', $params);
+            } else {
+                $DB->insert_record('course_completions', $params);
+            }
         }
     }
 
index 6f8ba31..38c72a8 100644 (file)
@@ -66,7 +66,7 @@ abstract class backup_factory {
 
         // Create database_logger, observing $CFG->backup_database_logger_level and defaulting to LOG_WARNING
         // and pointing to the backup_logs table
-        $dllevel = isset($CFG->backup_database_logger_level) ? $CFG->backup_database_logger_level : backup::LOG_WARNING;
+        $dllevel = isset($CFG->backup_database_logger_level) ? $CFG->backup_database_logger_level : $dfltloglevel;
         $columns = array('backupid' => $backupid);
         $enabledloggers[] = new database_logger($dllevel, 'timecreated', 'loglevel', 'message', 'backup_logs', $columns);
 
index 5adb973..671a8b5 100644 (file)
@@ -403,7 +403,7 @@ abstract class backup_cron_automated_helper {
                 $bc->log('No directory specified for automated backups',
                         backup::LOG_WARNING);
                 $outcome = self::BACKUP_STATUS_WARNING;
-            } else if (!file_exists($dir) || !is_dir($dir) || !is_writable($dir) && $storage !== 0) {
+            } else if ($storage !== 0 && (!file_exists($dir) || !is_dir($dir) || !is_writable($dir))) {
                 // If we need to copy the backup file to an external dir and it is not writable, change status to error.
                 $bc->log('Specified backup directory is not writable - ',
                         backup::LOG_ERROR, $dir);
index 99c099e..3632182 100644 (file)
@@ -140,7 +140,7 @@ class core_badges_renderer extends plugin_renderer_base {
         $dl = array();
         $dl[get_string('name')] = $badge->name;
         $dl[get_string('description', 'badges')] = $badge->description;
-        $dl[get_string('createdon', 'search')] = $badge->timecreated;
+        $dl[get_string('createdon', 'search')] = userdate($badge->timecreated);
         $dl[get_string('badgeimage', 'badges')] = print_badge_image($badge, $context, 'large');
         $display .= $this->definition_list($dl);
 
index 41cd98e..ace839e 100644 (file)
@@ -30,15 +30,6 @@ class block_calendar_month extends block_base {
         $this->title = get_string('pluginname', 'block_calendar_month');
     }
 
-    /**
-     * Return preferred_width.
-     *
-     * @return int
-     */
-    public function preferred_width() {
-        return 210;
-    }
-
     /**
      * Return the content of this block.
      *
index 281aba3..c2163fc 100644 (file)
@@ -74,10 +74,6 @@ class block_course_summary extends block_base {
         return true;
     }
 
-    function preferred_width() {
-        return 210;
-    }
-
 }
 
 
index 19624eb..ad32113 100644 (file)
@@ -84,6 +84,12 @@ class block_edit_form extends moodleform {
         $weightoptions[$last] = get_string('bracketlast', 'block', $last);
 
         $regionoptions = $this->page->theme->get_all_block_regions();
+        foreach ($this->page->blocks->get_regions() as $region) {
+            // Make sure to add all custom regions of this particular page too.
+            if (!isset($regionoptions[$region])) {
+                $regionoptions[$region] = $region;
+            }
+        }
 
         $parentcontext = context::instance_by_id($this->block->instance->parentcontextid);
         $mform->addElement('hidden', 'bui_parentcontextid', $parentcontext->id);
index bbbd72b..0061a9b 100644 (file)
@@ -326,11 +326,6 @@ class block_base {
             $correct = false;
         }
 
-        $width = $this->preferred_width();
-        if (!is_int($width) || $width <= 0) {
-            $errors[] = 'invalid_width';
-            $correct = false;
-        }
         return $correct;
     }
 
@@ -594,17 +589,6 @@ class block_base {
         return array('moodle/block:view', 'moodle/block:edit');
     }
 
-    // Methods deprecated in Moodle 2.0 ========================================
-
-    /**
-     * Default case: the block wants to be 180 pixels wide
-     * @deprecated since Moodle 2.0.
-     * @return int
-     */
-    function preferred_width() {
-        return 180;
-    }
-
     /**
      * Can be overridden by the block to prevent the block from being dockable.
      *
index 30b623c..2838651 100644 (file)
         $this->title = get_string('pluginname', 'block_rss_client');
     }
 
-    function preferred_width() {
-        return 210;
-    }
-
     function applicable_formats() {
         return array('all' => true, 'tag' => false);   // Needs work to make it work on tags MDL-11960
     }
             $feed->set_cache_duration($CFG->block_rss_client_timeout*60);
         }
 
-        if(debugging() && $feed->error()){
+        if ($CFG->debugdeveloper && $feed->error()) {
             return '<p>'. $feedrecord->url .' Failed with code: '.$feed->error().'</p>';
         }
 
             $feed->init();
 
             if ($feed->error()) {
-                mtrace ('error');
-                mtrace ('SimplePie failed with error:'.$feed->error());
+                mtrace('Error: could not load/find the RSS feed');
                 $status = false;
             } else {
                 mtrace ('ok');
index a0193af..77e3097 100644 (file)
@@ -89,7 +89,7 @@ class feed_edit_form extends moodleform {
         $rss->init();
 
         if ($rss->error()) {
-            $errors['url'] = get_string('errorloadingfeed', 'block_rss_client', $rss->error());
+            $errors['url'] = get_string('couldnotfindloadrssfeed', 'block_rss_client');
         } else {
             $this->title = $rss->get_title();
             $this->description = $rss->get_description();
index cb2db5c..cdd8291 100644 (file)
@@ -33,6 +33,7 @@ $string['clientshowchannellinklabel'] = 'Should a link to the original site (cha
 $string['clientshowimagelabel'] = 'Show channel image if available :';
 $string['configblock'] = 'Configure this block';
 $string['couldnotfindfeed'] = 'Could not find feed with id';
+$string['couldnotfindloadrssfeed'] = 'Could not find or load the RSS feed.';
 $string['customtitlelabel'] = 'Custom title (leave blank to use title supplied by feed):';
 $string['deletefeedconfirm'] = 'Are you sure you want to delete this feed?';
 $string['disabledrssfeeds'] = 'RSS feeds are disabled';
@@ -43,7 +44,6 @@ $string['editnewsfeeds'] = 'Edit news feeds';
 $string['editrssblock'] = 'Edit RSS headline block';
 $string['enableautodiscovery'] = 'Enable auto-discovery of feeds?';
 $string['enableautodiscovery_help'] = 'If enabled, feeds on web pages are found automatically. For example, if http://docs.moodle.org is entered, then http://docs.moodle.org/en/index.php?title=Special:RecentChanges&feed=rss would be found.';
-$string['errorloadingfeed'] = 'Error loading this RSS feed ({$a})';
 $string['feed'] = 'Feed';
 $string['feedadded'] = 'News feed added';
 $string['feeddeleted'] = 'News feed deleted';
index 46f8c19..06f691d 100644 (file)
@@ -43,10 +43,6 @@ class block_tag_flickr extends block_base {
         return true;
     }
 
-    function preferred_width() {
-        return 170;
-    }
-
     function get_content() {
         global $CFG, $USER;
 
index 3779d42..a06588e 100644 (file)
@@ -45,10 +45,6 @@ class block_tag_youtube extends block_base {
         return true;
     }
 
-    function preferred_width() {
-        return 140;
-    }
-
     function get_content() {
         global $CFG;
 
index abd99d2..288eff3 100644 (file)
@@ -1,6 +1,10 @@
 This files describes API changes in /blocks/* - activity modules,
 information provided here is intended especially for developers.
 
+=== 2.9 ===
+
+* The obsolete method preferred_width() was removed (it was not doing anything)
+
 === 2.8 ===
 
 * The instance_config_print() function was removed. It was deprecated in
index f3765a4..6b8ce0a 100644 (file)
@@ -26,7 +26,6 @@
 require_once("../config.php");
 require_once("lib.php");
 require_once($CFG->libdir . '/formslib.php');
-require_once($CFG->libdir . '/conditionlib.php');
 
 $id = required_param('id', PARAM_INT);    // course_sections.id
 $sectionreturn = optional_param('sr', 0, PARAM_INT);
index 07cd6ed..b8212d5 100644 (file)
@@ -998,9 +998,6 @@ function get_array_of_activities($courseid) {
 //  groupingid - grouping id
 //  extra - contains extra string to include in any link
     global $CFG, $DB;
-    if(!empty($CFG->enableavailability)) {
-        require_once($CFG->libdir.'/conditionlib.php');
-    }
 
     $course = $DB->get_record('course', array('id'=>$courseid));
 
index b2d8ede..234e3ad 100644 (file)
@@ -28,7 +28,6 @@ require_once("lib.php");
 require_once($CFG->libdir.'/filelib.php');
 require_once($CFG->libdir.'/gradelib.php');
 require_once($CFG->libdir.'/completionlib.php');
-require_once($CFG->libdir.'/conditionlib.php');
 require_once($CFG->libdir.'/plagiarismlib.php');
 require_once($CFG->dirroot . '/course/modlib.php');
 
index fa1ac35..9a44714 100644 (file)
@@ -483,12 +483,17 @@ function update_moduleinfo($cm, $moduleinfo, $course, $mform = null) {
     }
 
     $completion = new completion_info($course);
-    if ($completion->is_enabled() && !empty($moduleinfo->completionunlocked)) {
-        // Update completion settings.
-        $cm->completion                = $moduleinfo->completion;
-        $cm->completiongradeitemnumber = $moduleinfo->completiongradeitemnumber;
-        $cm->completionview            = $moduleinfo->completionview;
-        $cm->completionexpected        = $moduleinfo->completionexpected;
+    if ($completion->is_enabled()) {
+        // Completion settings that would affect users who have already completed
+        // the activity may be locked; if so, these should not be updated.
+        if (!empty($moduleinfo->completionunlocked)) {
+            $cm->completion = $moduleinfo->completion;
+            $cm->completiongradeitemnumber = $moduleinfo->completiongradeitemnumber;
+            $cm->completionview = $moduleinfo->completionview;
+        }
+        // The expected date does not affect users who have completed the activity,
+        // so it is safe to update it regardless of the lock status.
+        $cm->completionexpected = $moduleinfo->completionexpected;
     }
     if (!empty($CFG->enableavailability)) {
         // This code is used both when submitting the form, which uses a long
index 75ce9c9..170e308 100644 (file)
@@ -40,7 +40,7 @@ if ($return === 'management') {
 $PAGE->set_url($url);
 
 // Check permissions.
-require_login();
+require_login(null, false);
 if (isguestuser()) {
     print_error('guestsarenotallowed', '', $returnurl);
 }
diff --git a/course/tests/behat/activities_edit_completion.feature b/course/tests/behat/activities_edit_completion.feature
new file mode 100644 (file)
index 0000000..e099938
--- /dev/null
@@ -0,0 +1,57 @@
+@core @core_course
+Feature: Edit completion settings of an activity
+  In order to edit completion settings without accidentally breaking user data
+  As a teacher
+  I need to edit the activity and use the unlock button if required
+
+  Background:
+    Given I log in as "admin"
+    And I set the following administration settings values:
+      | Enable completion tracking | 1 |
+    And I log out
+    And the following "courses" exist:
+      | fullname | shortname | enablecompletion |
+      | Course 1 | C1        | 1                |
+    And the following "activities" exist:
+      | activity | course | idnumber | name     | intro | content | completion | completionview |
+      | page     | C1     | x        | TestPage | x     | x       | 2          | 1              |
+    And I log in as "admin"
+    And I follow "Course 1"
+
+  Scenario: Completion is not locked when the activity has not yet been viewed
+    Given I turn editing mode on
+    And I click on "Edit settings" "link" in the "TestPage" activity
+    When I expand all fieldsets
+    Then I should see "Completion tracking"
+    And I should not see "Completion options locked"
+
+  Scenario: Completion is locked after the activity has been viewed
+    Given I follow "TestPage"
+    When I follow "Edit settings"
+    And I expand all fieldsets
+    Then I should see "Completion options locked"
+
+  @javascript
+  Scenario: Pressing the unlock button allows the user to edit completion settings
+    Given I follow "TestPage"
+    When I follow "Edit settings"
+    And I expand all fieldsets
+    And I press "Unlock completion options"
+    Then I should see "Completion options unlocked"
+    And I set the field "Completion tracking" to "Students can manually mark the activity as completed"
+    And I press "Save and display"
+    And I follow "Edit settings"
+    And I expand all fieldsets
+    Then the field "Completion tracking" matches value "Students can manually mark the activity as completed"
+
+  @javascript
+  Scenario: Even when completion is locked, the user can still set the date
+    Given I follow "TestPage"
+    And I follow "Edit settings"
+    And I expand all fieldsets
+    When I click on "id_completionexpected_enabled" "checkbox"
+    And I set the field "id_completionexpected_year" to "2013"
+    And I press "Save and display"
+    And I follow "Edit settings"
+    And I expand all fieldsets
+    Then the field "id_completionexpected_year" matches value "2013"
index 77ef9e0..9c38afe 100644 (file)
@@ -233,7 +233,7 @@ class core_course_courselib_testcase extends advanced_testcase {
                 ']}';
         $coursegradeitem = grade_item::fetch_course_item($moduleinfo->course); //the activity will become available only when the user reach some grade into the course itself.
         $moduleinfo->conditiongradegroup = array(array('conditiongradeitemid' => $coursegradeitem->id, 'conditiongrademin' => 10, 'conditiongrademax' => 80));
-        $moduleinfo->conditionfieldgroup = array(array('conditionfield' => 'email', 'conditionfieldoperator' => OP_CONTAINS, 'conditionfieldvalue' => '@'));
+        $moduleinfo->conditionfieldgroup = array(array('conditionfield' => 'email', 'conditionfieldoperator' => \availability_profile\condition::OP_CONTAINS, 'conditionfieldvalue' => '@'));
         $moduleinfo->conditioncompletiongroup = array(array('conditionsourcecmid' => $assigncm->id, 'conditionrequiredcompletion' => COMPLETION_COMPLETE)); // "conditionsourcecmid == 0" => none
 
         // Grading and Advanced grading.
index 9f70969..37bd1a8 100644 (file)
@@ -335,6 +335,8 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
 
         // Enable course completion.
         set_config('enablecompletion', 1);
+        // Enable course themes.
+        set_config('allowcoursethemes', 1);
 
         // Set the required capabilities by the external function
         $contextid = context_system::instance()->id;
@@ -415,10 +417,7 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
                 $this->assertEquals($courseinfo->defaultgroupingid, $course2['defaultgroupingid']);
                 $this->assertEquals($courseinfo->completionnotify, $course2['completionnotify']);
                 $this->assertEquals($courseinfo->lang, $course2['lang']);
-
-                if (!empty($CFG->allowcoursethemes)) {
-                    $this->assertEquals($courseinfo->theme, $course2['forcetheme']);
-                }
+                $this->assertEquals($courseinfo->theme, $course2['forcetheme']);
 
                 // We enabled completion at the beginning of the test.
                 $this->assertEquals($courseinfo->enablecompletion, $course2['enablecompletion']);
index 3cc2d0c..f406a06 100644 (file)
@@ -4,7 +4,6 @@
 
     require_once('../config.php');
     require_once('lib.php');
-    require_once($CFG->libdir.'/conditionlib.php');
     require_once($CFG->libdir.'/completionlib.php');
 
     $id          = optional_param('id', 0, PARAM_INT);
index 6eebc68..9fb6c3a 100644 (file)
Binary files a/course/yui/build/moodle-course-toolboxes/moodle-course-toolboxes-debug.js and b/course/yui/build/moodle-course-toolboxes/moodle-course-toolboxes-debug.js differ
index e258f95..9be9edb 100644 (file)
Binary files a/course/yui/build/moodle-course-toolboxes/moodle-course-toolboxes-min.js and b/course/yui/build/moodle-course-toolboxes/moodle-course-toolboxes-min.js differ
index 6eebc68..9fb6c3a 100644 (file)
Binary files a/course/yui/build/moodle-course-toolboxes/moodle-course-toolboxes.js and b/course/yui/build/moodle-course-toolboxes/moodle-course-toolboxes.js differ
index 83139a3..228cff2 100644 (file)
@@ -423,10 +423,17 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
 
         // If activity is conditionally hidden, then don't toggle.
         if (!dimarea.hasClass(CSS.CONDITIONALHIDDEN)) {
-            // Change the UI.
-            dimarea.toggleClass(toggleclass);
-            // We need to toggle dimming on the description too.
-            activity.all(SELECTOR.CONTENTAFTERLINK).toggleClass(CSS.DIMMEDTEXT);
+            if (action === 'hide') {
+                // Change the UI.
+                dimarea.addClass(toggleclass);
+                // We need to toggle dimming on the description too.
+                activity.all(SELECTOR.CONTENTAFTERLINK).addClass(CSS.DIMMEDTEXT);
+            } else {
+                // Change the UI.
+                dimarea.removeClass(toggleclass);
+                // We need to toggle dimming on the description too.
+                activity.all(SELECTOR.CONTENTAFTERLINK).removeClass(CSS.DIMMEDTEXT);
+            }
         }
         // Toggle availablity info for conditional activities.
         if (availabilityinfo) {
index 21ec3d6..c7d0e2a 100644 (file)
@@ -65,7 +65,7 @@ class enrol_cohort_edit_form extends moodleform {
 
         } else {
             $cohorts = array('' => get_string('choosedots'));
-            $allcohorts = cohort_get_available_cohorts($coursecontext);
+            $allcohorts = cohort_get_available_cohorts($coursecontext, 0, 0, 0);
             foreach ($allcohorts as $c) {
                 $cohorts[$c->id] = format_string($c->name);
             }
index 0f338fb..1f5a221 100644 (file)
@@ -101,7 +101,7 @@ class enrol_cohort_plugin extends enrol_plugin {
         if (!has_capability('moodle/course:enrolconfig', $coursecontext) or !has_capability('enrol/cohort:config', $coursecontext)) {
             return false;
         }
-        return cohort_get_available_cohorts($coursecontext) ? true : false;
+        return cohort_get_available_cohorts($coursecontext, 0, 0, 1) ? true : false;
     }
 
     /**
index 34d4d30..4b035f3 100644 (file)
@@ -247,7 +247,7 @@ class enrol_manual_plugin extends enrol_plugin {
             'defaultDuration'     => $defaultduration,
             'disableGradeHistory' => $CFG->disablegradehistory,
             'recoverGradesDefault'=> '',
-            'cohortsAvailable'    => cohort_get_available_cohorts($manager->get_context(), COHORT_COUNT_MEMBERS, 0, 1) ? true : false
+            'cohortsAvailable'    => cohort_get_available_cohorts($manager->get_context(), COHORT_WITH_NOTENROLLED_MEMBERS_ONLY, 0, 1) ? true : false
         );
 
         if ($CFG->recovergradesdefault) {
index 9cdd3c3..d2c5972 100644 (file)
@@ -48,6 +48,15 @@ $capabilities = array(
         )
     ),
 
+    'enrol/self:holdkey' => array(
+
+        'captype' => 'write',
+        'contextlevel' => CONTEXT_COURSE,
+        'archetypes' => array(
+            'manager' => CAP_PROHIBIT,
+        )
+    ),
+
     /* Voluntarily unenrol self from course - watch out for data loss. */
     'enrol/self:unenrolself' => array(
         'captype' => 'write',
index e33da18..a1e3a80 100644 (file)
@@ -106,7 +106,7 @@ class enrol_self_edit_form extends moodleform {
         $mform->setType('customint3', PARAM_INT);
 
         $cohorts = array(0 => get_string('no'));
-        $allcohorts = cohort_get_available_cohorts($context);
+        $allcohorts = cohort_get_available_cohorts($context, 0, 0, 0);
         if ($instance->customint5 && !isset($allcohorts[$instance->customint5]) &&
                 ($c = $DB->get_record('cohort', array('id' => $instance->customint5), 'id, name, idnumber, contextid, visible', IGNORE_MISSING))) {
             // Current cohort was not found because current user can not see it. Still keep it.
index a964a9f..87a43f0 100644 (file)
@@ -63,6 +63,7 @@ $string['groupkey_desc'] = 'Use group enrolment keys by default.';
 $string['groupkey_help'] = 'In addition to restricting access to the course to only those who know the key, use of group enrolment keys means users are automatically added to groups when they enrol in the course.
 
 Note: An enrolment key for the course must be specified in the self enrolment settings as well as group enrolment keys in the group settings.';
+$string['keyholder'] = 'You should have received this enrolment key from:';
 $string['longtimenosee'] = 'Unenrol inactive after';
 $string['longtimenosee_help'] = 'If users haven\'t accessed a course for a long time, then they are automatically unenrolled. This parameter specifies that time limit.';
 $string['maxenrolled'] = 'Max enrolled users';
@@ -88,6 +89,7 @@ $string['requirepassword'] = 'Require enrolment key';
 $string['requirepassword_desc'] = 'Require enrolment key in new courses and prevent removing of enrolment key from existing courses.';
 $string['role'] = 'Default assigned role';
 $string['self:config'] = 'Configure self enrol instances';
+$string['self:holdkey'] = 'Appear as the self enrolment key holder';
 $string['self:manage'] = 'Manage enrolled users';
 $string['self:unenrol'] = 'Unenrol users from course';
 $string['self:unenrolself'] = 'Unenrol self from the course';
index cd010eb..226555d 100644 (file)
@@ -41,6 +41,7 @@ class enrol_self_enrol_form extends moodleform {
     }
 
     public function definition() {
+        global $USER, $OUTPUT, $CFG;
         $mform = $this->_form;
         $instance = $this->_customdata;
         $this->instance = $instance;
@@ -53,6 +54,26 @@ class enrol_self_enrol_form extends moodleform {
             // Change the id of self enrolment key input as there can be multiple self enrolment methods.
             $mform->addElement('passwordunmask', 'enrolpassword', get_string('password', 'enrol_self'),
                     array('id' => 'enrolpassword_'.$instance->id));
+            $context = context_course::instance($this->instance->courseid);
+            $keyholders = get_users_by_capability($context, 'enrol/self:holdkey', user_picture::fields('u'));
+            $keyholdercount = 0;
+            foreach ($keyholders as $keyholder) {
+                $keyholdercount++;
+                if ($keyholdercount === 1) {
+                    $mform->addElement('static', 'keyholder', '', get_string('keyholder', 'enrol_self'));
+                }
+                $keyholdercontext = context_user::instance($keyholder->id);
+                if ($USER->id == $keyholder->id || has_capability('moodle/user:viewdetails', context_system::instance()) ||
+                        has_coursecontact_role($keyholder->id)) {
+                    $profilelink = '<a href="' . $CFG->wwwroot . '/user/view.php?id=' . $keyholder->id . '&amp;course=' .
+                    $this->instance->courseid . '">' . fullname($keyholder) . '</a>';
+                } else {
+                    $profilelink = fullname($keyholder);
+                }
+                $profilepic = $OUTPUT->user_picture($keyholder, array('size' => 35, 'courseid' => $this->instance->courseid));
+                $mform->addElement('static', 'keyholder'.$keyholdercount, '', $profilepic . $profilelink);
+            }
+
         } else {
             $mform->addElement('static', 'nokey', '', get_string('nopassword', 'enrol_self'));
         }
index adc754e..59c2ca0 100644 (file)
@@ -24,7 +24,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2014111000;        // The current plugin version (Date: YYYYMMDDXX)
+$plugin->version   = 2014111001;        // The current plugin version (Date: YYYYMMDDXX)
 $plugin->requires  = 2014110400;        // Requires this Moodle version
 $plugin->component = 'enrol_self';      // Full name of the plugin (used for diagnostics)
 $plugin->cron      = 600;
index 49e272e..e878fa6 100644 (file)
@@ -189,6 +189,9 @@ class core_files_renderer extends plugin_renderer_base {
         $strdndnotsupported = get_string('dndnotsupported_insentence', 'moodle').$OUTPUT->help_icon('dndnotsupported');
         $strdndenabledinbox = get_string('dndenabled_inbox', 'moodle');
         $loading = get_string('loading', 'repository');
+        $straddfiletext = get_string('addfiletext', 'repository');
+        $strcreatefolder = get_string('createfolder', 'repository');
+        $strdownloadallfiles = get_string('downloadallfiles', 'repository');
 
         $html = '
 <div id="filemanager-'.$client_id.'" class="filemanager fm-loading">
@@ -200,25 +203,31 @@ class core_files_renderer extends plugin_renderer_base {
         <div class="filemanager-toolbar">
             <div class="fp-toolbar">
                 <div class="fp-btn-add">
-                    <a role="button" title="'.$straddfile.'" href="#"><img src="'.$this->pix_url('a/add_file').'" alt="" /></a>
+                    <a role="button" title="' . $straddfile . '" href="#">
+                        <img src="' . $this->pix_url('a/add_file') . '" alt="' . $straddfiletext . '" />
+                    </a>
                 </div>
                 <div class="fp-btn-mkdir">
-                    <a role="button" title="'.$strmakedir.'" href="#"><img src="'.$this->pix_url('a/create_folder').'" alt="" /></a>
+                    <a role="button" title="' . $strmakedir . '" href="#">
+                        <img src="' . $this->pix_url('a/create_folder') . '" alt="' . $strcreatefolder . '" />
+                    </a>
                 </div>
                 <div class="fp-btn-download">
-                    <a role="button" title="'.$strdownload.'" href="#"><img src="'.$this->pix_url('a/download_all').'" alt="" /></a>
+                    <a role="button" title="' . $strdownload . '" href="#">
+                        <img src="' . $this->pix_url('a/download_all').'" alt="' . $strdownloadallfiles . '" />
+                    </a>
                 </div>
                 <img class="fp-img-downloading" src="'.$this->pix_url('i/loading_small').'" alt="" />
             </div>
             <div class="fp-viewbar">
                 <a title="'. get_string('displayicons', 'repository') .'" class="fp-vb-icons" href="#">
-                    <img alt="" src="'. $this->pix_url('fp/view_icon_active', 'theme') .'" />
+                    <img alt="'. get_string('displayasicons', 'repository') .'" src="'. $this->pix_url('fp/view_icon_active', 'theme') .'" />
                 </a>
                 <a title="'. get_string('displaydetails', 'repository') .'" class="fp-vb-details" href="#">
-                    <img alt="" src="'. $this->pix_url('fp/view_list_active', 'theme') .'" />
+                    <img alt="'. get_string('displayasdetails', 'repository') .'" src="'. $this->pix_url('fp/view_list_active', 'theme') .'" />
                 </a>
                 <a title="'. get_string('displaytree', 'repository') .'" class="fp-vb-tree" href="#">
-                    <img alt="" src="'. $this->pix_url('fp/view_tree_active', 'theme') .'" />
+                    <img alt="'. get_string('displayastree', 'repository') .'" src="'. $this->pix_url('fp/view_tree_active', 'theme') .'" />
                 </a>
             </div>
         </div>
index fe90e41..67b5fd7 100644 (file)
@@ -427,11 +427,6 @@ class grade_edit_tree {
             );
 
             $str .= $checkboxlbl . $checkbox . $hiddenlabel . $input;
-
-            if ($item->aggregationcoef > 0) {
-                $str .= ' ' . html_writer::tag('abbr', get_string('aggregationcoefextrasumabbr', 'grades'),
-                        array('title' => get_string('aggregationcoefextrasum', 'grades')));
-            }
         }
 
         return $str;
@@ -771,6 +766,11 @@ class grade_edit_tree_column_range extends grade_edit_tree_column {
             $grademax = format_float($item->grademax, $item->get_decimals());
         }
 
+        if ($item->aggregationcoef > 0 && $parent_cat->is_extracredit_used()) {
+            $grademax .= ' ' . html_writer::tag('abbr', get_string('aggregationcoefextrasumabbr', 'grades'),
+                array('title' => get_string('aggregationcoefextrasum', 'grades')));
+        }
+
         $itemcell = parent::get_item_cell($item, $params);
         $itemcell->text = $grademax;
         return $itemcell;
index c4d4810..4d3b831 100644 (file)
@@ -57,6 +57,10 @@ if ($mform->is_cancelled()) {
     redirect($returnurl);
 }
 
+// Try to keep the session alive on this page as it may take some time
+// before significant interaction happens with the server.
+\core\session\manager::keepalive();
+
 echo $OUTPUT->header();
 $mform->display();
-echo $OUTPUT->footer();
\ No newline at end of file
+echo $OUTPUT->footer();
index 822a758..f15efe2 100644 (file)
@@ -40,8 +40,8 @@ $target        = optional_param('target', 0, PARAM_ALPHANUM);
 $toggle        = optional_param('toggle', null, PARAM_INT);
 $toggle_type   = optional_param('toggle_type', 0, PARAM_ALPHANUM);
 
-$graderreportsifirst  = optional_param('sifirst', null, PARAM_ALPHA);
-$graderreportsilast   = optional_param('silast', null, PARAM_ALPHA);
+$graderreportsifirst  = optional_param('sifirst', null, PARAM_NOTAGS);
+$graderreportsilast   = optional_param('silast', null, PARAM_NOTAGS);
 
 // The report object is recreated each time, save search information to SESSION object for future use.
 if (isset($graderreportsifirst)) {
index 51346d1..2905a9a 100644 (file)
@@ -121,17 +121,9 @@ class grade extends tablelike implements selectable_items, filterable_items {
      * @param bool $selfitemisempty True if we have not selected a user.
      */
     public function init($selfitemisempty = false) {
-        $roleids = explode(',', get_config('moodle', 'gradebookroles'));
-
-        $this->items = array();
-        foreach ($roleids as $roleid) {
-            // Keeping the first user appearance.
-            $this->items = $this->items + get_role_users(
-                $roleid, $this->context, false, '',
-                'u.lastname, u.firstname', null, $this->groupid);
-        }
 
-        $this->totalitemcount = count_role_users($roleids, $this->context);
+        $this->items = $this->load_users();
+        $this->totalitemcount = count($this->items);
 
         if ($selfitemisempty) {
             return;
@@ -273,7 +265,7 @@ class grade extends tablelike implements selectable_items, filterable_items {
             new moodle_url('/grade/report/singleview/index.php', array(
                 'perpage' => $this->perpage,
                 'id' => $this->courseid,
-                'groupid' => $this->groupid,
+                'group' => $this->groupid,
                 'itemid' => $this->itemid,
                 'item' => 'grade'
             ))
index 0550af3..f31c1d2 100644 (file)
@@ -375,4 +375,29 @@ abstract class screen {
     public function supports_next_prev() {
         return true;
     }
+
+    /**
+     * Load a valid list of users for this gradebook as the screen "items".
+     * @return array $users A list of enroled users.
+     */
+    protected function load_users() {
+        global $CFG;
+
+        // Create a graded_users_iterator because it will properly check the groups etc.
+        $defaultgradeshowactiveenrol = !empty($CFG->grade_report_showonlyactiveenrol);
+        $showonlyactiveenrol = get_user_preferences('grade_report_showonlyactiveenrol', $defaultgradeshowactiveenrol);
+        $showonlyactiveenrol = $showonlyactiveenrol || !has_capability('moodle/course:viewsuspendedusers', $this->context);
+
+        require_once($CFG->dirroot.'/grade/lib.php');
+        $gui = new \graded_users_iterator($this->course, null, $this->groupid);
+        $gui->require_active_enrolment($showonlyactiveenrol);
+        $gui->init();
+
+        // Flatten the users.
+        $users = array();
+        while ($user = $gui->next_user()) {
+            $users[$user->user->id] = $user->user;
+        }
+        return $users;
+    }
 }
index cf3fddd..6993237 100644 (file)
@@ -101,7 +101,11 @@ class user extends tablelike implements selectable_items {
         global $DB;
 
         if (!$selfitemisempty) {
-            $this->item = $DB->get_record('user', array('id' => $this->itemid));
+            $validusers = $this->load_users();
+            if (!isset($validusers[$this->itemid])) {
+                print_error('invaliduserid');
+            }
+            $this->item = $validusers[$this->itemid];
         }
 
         $params = array('courseid' => $this->courseid);
@@ -307,7 +311,7 @@ class user extends tablelike implements selectable_items {
             new moodle_url('/grade/report/singleview/index.php', array(
                 'perpage' => $this->perpage,
                 'id' => $this->courseid,
-                'groupid' => $this->groupid,
+                'group' => $this->groupid,
                 'itemid' => $this->itemid,
                 'item' => 'user'
             ))
index dd5348c..d57083f 100644 (file)
@@ -48,7 +48,8 @@ class finalgrade extends grade_attribute_format implements unique_value, be_disa
         $this->label = $this->grade->grade_item->itemname;
 
         $isoverridden = $this->grade->is_overridden();
-        if (!empty($isoverridden)) {
+        // If the grade is overridden or the grade type is not an activity then use finalgrade.
+        if (!empty($isoverridden) || $this->grade->grade_item->itemtype != 'mod') {
             $val = $this->grade->finalgrade;
         } else {
             $val = $this->grade->rawgrade;
index a355b28..42e99bf 100644 (file)
@@ -76,10 +76,7 @@ $USER->grade_last_report[$course->id] = 'singleview';
 // this must be done before constructing of the grade tree.
 grade_regrade_final_grades($courseid);
 
-$report = new gradereport_singleview(
-    $courseid, $gpr, $context,
-    $itemtype, $itemid, $groupid
-);
+$report = new gradereport_singleview($courseid, $gpr, $context, $itemtype, $itemid);
 
 $reportname = $report->screen->heading();
 
@@ -130,7 +127,7 @@ if (!empty($options)) {
 
     $relreport = new gradereport_singleview(
                 $courseid, $gpr, $context,
-                $report->screen->item_type(), $optionitemid, $groupid
+                $report->screen->item_type(), $optionitemid
     );
     $reloptions = $relreport->screen->options();
     $reloptionssorting = array_keys($relreport->screen->options());
index 5382c6c..b40711b 100644 (file)
@@ -74,18 +74,11 @@ class gradereport_singleview extends grade_report {
      * @param context_course $context
      * @param string $itemtype Should be user, select or grade
      * @param int $itemid The id of the user or grade item
-     * @param int $groupid (optional) The current groupid.
+     * @param string $unused Used to be group id but that was removed and this is now unused.
      */
-    public function __construct($courseid, $gpr, $context, $itemtype, $itemid, $groupid=null) {
+    public function __construct($courseid, $gpr, $context, $itemtype, $itemid, $unused = null) {
         parent::__construct($courseid, $gpr, $context);
 
-        $screenclass = "\\gradereport_singleview\\local\\screen\\${itemtype}";
-
-        $this->screen = new $screenclass($courseid, $itemid, $groupid);
-
-        // Load custom or predifined js.
-        $this->screen->js();
-
         $base = '/grade/report/singleview/index.php';
 
         $idparams = array('id' => $courseid);
@@ -93,11 +86,19 @@ class gradereport_singleview extends grade_report {
         $this->baseurl = new moodle_url($base, $idparams);
 
         $this->pbarurl = new moodle_url($base, $idparams + array(
-            'item' => $itemtype,
-            'itemid' => $itemid
-        ));
+                'item' => $itemtype,
+                'itemid' => $itemid
+            ));
 
+        //  The setup_group method is used to validate group mode and permissions and define the currentgroup value.
         $this->setup_groups();
+
+        $screenclass = "\\gradereport_singleview\\local\\screen\\${itemtype}";
+
+        $this->screen = new $screenclass($courseid, $itemid, $this->currentgroup);
+
+        // Load custom or predifined js.
+        $this->screen->js();
     }
 
     /**
index d33a7e0..7e4197a 100644 (file)
@@ -66,6 +66,13 @@ Feature: We can use Single view
     And the following should exist in the "generaltable" table:
         | Test assignment one |
         | 10.00 |
+    And I set the following fields to these values:
+        | Test grade item | 45 |
+    And I press "Update"
+    Then I should see "Grades were set for 1 items"
+    And I press "Continue"
+    And the field "Grade for Test grade item" matches value "45.00"
+    And the field "Grade for Course total" matches value "55.00"
     And I click on "Show grades for Test assignment three" "link"
     And I click on "Override for james (Student) 1" "checkbox"
     And I set the following fields to these values:
diff --git a/grade/report/singleview/tests/fixtures/screen.php b/grade/report/singleview/tests/fixtures/screen.php
new file mode 100644 (file)
index 0000000..33ae6e3
--- /dev/null
@@ -0,0 +1,52 @@
+<?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/>.
+
+/**
+ * Fixtures for single view report screen class testing.
+ *
+ * @package    gradereport_singleview
+ * @copyright  2014 onwards Simey Lameze <simey@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+class gradereport_singleview_screen_testable extends \gradereport_singleview\local\screen\screen {
+
+    /**
+     * Wrapper to make protected method accessible during testing.
+     *
+     * @return array returns array of users.
+     */
+    public function test_load_users() {
+        return $this->load_users();
+    }
+
+    /**
+     * Return the HTML for the page.
+     */
+    public function init($selfitemisempty = false) {}
+
+    /**
+     * Get the type of items on this screen, not valid so return false.
+     */
+    public function item_type() {}
+
+    /**
+     * Return the HTML for the page.
+     */
+    public function html() {}
+}
diff --git a/grade/report/singleview/tests/screen_test.php b/grade/report/singleview/tests/screen_test.php
new file mode 100644 (file)
index 0000000..2476d08
--- /dev/null
@@ -0,0 +1,92 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Unit tests for gradereport_singleview screen class.
+ *
+ * @package    gradereport_singleview
+ * @category   test
+ * @copyright  2014 onwards Simey Lameze <simey@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+
+require_once(__DIR__ . '/fixtures/screen.php');
+
+defined('MOODLE_INTERNAL') || die();
+/**
+ * Tests for screen class.
+ *
+ * Class gradereport_singleview_screen_testcase.
+ */
+class gradereport_singleview_screen_testcase extends advanced_testcase {
+
+    /**
+     * Test load_users method.
+     */
+    public function test_load_users() {
+        global $DB;
+
+        $this->setAdminUser();
+        $this->resetAfterTest(true);
+
+        $roleteacher = $DB->get_record('role', array('shortname' => 'teacher'), '*', MUST_EXIST);
+
+        // Create a course, users and groups.
+        $course = $this->getDataGenerator()->create_course();
+        $coursecontext = context_course::instance($course->id);
+        $group = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
+        $teacher = $this->getDataGenerator()->create_user();
+        $user1 = $this->getDataGenerator()->create_user();
+        $user2 = $this->getDataGenerator()->create_user();
+        $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $roleteacher->id);
+        $this->getDataGenerator()->enrol_user($user1->id, $course->id);
+        $this->getDataGenerator()->enrol_user($user2->id, $course->id);
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group->id, 'userid' => $teacher->id));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group->id, 'userid' => $user1->id));
+        $this->getDataGenerator()->create_group_member(array('groupid' => $group->id, 'userid' => $user2->id));
+
+        $screentest = new gradereport_singleview_screen_testable($course->id, 0, $group->id);
+        $groupusers = $screentest->test_load_users();
+        $this->assertCount(2, $groupusers);
+
+        // Now, let's suspend the enrolment of a user. Should return only one user.
+        $this->getDataGenerator()->enrol_user($user2->id, $course->id, $roleteacher->id, 'manual', 0, 0, ENROL_USER_SUSPENDED);
+        $users = $screentest->test_load_users();
+        $this->assertCount(1, $users);
+
+        // Change the viewsuspendedusers capabilities and set the user preference to display suspended users.
+        assign_capability('moodle/course:viewsuspendedusers', CAP_ALLOW, $roleteacher->id, $coursecontext, true);
+        set_user_preference('grade_report_showonlyactiveenrol', false, $teacher);
+        accesslib_clear_all_caches_for_unit_testing();
+        $this->setUser($teacher);
+        $screentest = new gradereport_singleview_screen_testable($course->id, 0, $group->id);
+        $users = $screentest->test_load_users();
+        $this->assertCount(2, $users);
+
+        // Change the capability again, now the user can't see the suspended enrolments.
+        assign_capability('moodle/course:viewsuspendedusers', CAP_PROHIBIT, $roleteacher->id, $coursecontext, true);
+        set_user_preference('grade_report_showonlyactiveenrol', false, $teacher);
+        accesslib_clear_all_caches_for_unit_testing();
+        $users = $screentest->test_load_users();
+        $this->assertCount(1, $users);
+
+        // Now, activate the user enrolment again. We shall get 2 users now.
+        $this->getDataGenerator()->enrol_user($user2->id, $course->id, $roleteacher->id, 'manual', 0, 0, ENROL_USER_ACTIVE);
+        $users = $screentest->test_load_users();
+        $this->assertCount(2, $users);
+    }
+}
\ No newline at end of file
index 748b210..8c10d0b 100644 (file)
@@ -1,6 +1,9 @@
 This files describes API changes in /grade/report/*,
 information provided here is intended especially for developers.
 
+=== 2.8.2 ===
+* gradereport_singleview::__construct doesn't need groupid parameter anymore, so it was renamed to $unused.
+
 === 2.6.5, 2.7.2 ===
 
 * The callback function grade_report_*_profilereport now takes one more parameter $viewasuser. This parameter
index df43b4f..a04da01 100644 (file)
@@ -82,7 +82,7 @@ class autogroup_form extends moodleform {
             $mform->setDefault('roleid', $student->id);
         }
 
-        if ($cohorts = cohort_get_available_cohorts(context_course::instance($COURSE->id), COHORT_WITH_ENROLLED_MEMBERS_ONLY)) {
+        if ($cohorts = cohort_get_available_cohorts(context_course::instance($COURSE->id), COHORT_WITH_ENROLLED_MEMBERS_ONLY, 0, 0)) {
             $options = array(0 => get_string('anycohort', 'cohort'));
             foreach ($cohorts as $c) {
                 $options[$c->id] = format_string($c->name, true, context::instance_by_id($c->contextid));
index 5b5a65f..f59b2b6 100644 (file)
--- a/help.php
+++ b/help.php
@@ -42,6 +42,11 @@ $PAGE->set_pagelayout('popup');
 $PAGE->set_context(context_system::instance());
 
 $data = get_formatted_help_string($identifier, $component, false);
+if (!empty($data->heading)) {
+    $PAGE->set_title($data->heading);
+} else {
+    $PAGE->set_title(get_string('help'));
+}
 echo $OUTPUT->header();
 if (!empty($data->heading)) {
     echo $OUTPUT->heading($data->heading, 1, 'helpheading');
index 2a19e45..5d5e0f7 100644 (file)
--- a/index.php
+++ b/index.php
@@ -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
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
-    if (!file_exists('./config.php')) {
-        header('Location: install.php');
-        die;
-    }
-
-    require_once('config.php');
-    require_once($CFG->dirroot .'/course/lib.php');
-    require_once($CFG->libdir .'/filelib.php');
-
-    redirect_if_major_upgrade_required();
-
-    $urlparams = array();
-    if (!empty($CFG->defaulthomepage) && ($CFG->defaulthomepage == HOMEPAGE_MY) && optional_param('redirect', 1, PARAM_BOOL) === 0) {
-        $urlparams['redirect'] = 0;
+if (!file_exists('./config.php')) {
+    header('Location: install.php');
+    die;
+}
+
+require_once('config.php');
+require_once($CFG->dirroot .'/course/lib.php');
+require_once($CFG->libdir .'/filelib.php');
+
+redirect_if_major_upgrade_required();
+
+$urlparams = array();
+if (!empty($CFG->defaulthomepage) && ($CFG->defaulthomepage == HOMEPAGE_MY) && optional_param('redirect', 1, PARAM_BOOL) === 0) {
+    $urlparams['redirect'] = 0;
+}
+$PAGE->set_url('/', $urlparams);
+$PAGE->set_course($SITE);
+$PAGE->set_other_editing_capability('moodle/course:update');
+$PAGE->set_other_editing_capability('moodle/course:manageactivities');
+$PAGE->set_other_editing_capability('moodle/course:activityvisibility');
+
+// Prevent caching of this page to stop confusion when changing page after making AJAX changes.
+$PAGE->set_cacheable(false);
+
+if ($CFG->forcelogin) {
+    require_login();
+} else {
+    user_accesstime_log();
+}
+
+$hassiteconfig = has_capability('moodle/site:config', context_system::instance());
+
+// If the site is currently under maintenance, then print a message.
+if (!empty($CFG->maintenance_enabled) and !$hassiteconfig) {
+    print_maintenance_message();
+}
+
+if ($hassiteconfig && moodle_needs_upgrading()) {
+    redirect($CFG->wwwroot .'/'. $CFG->admin .'/index.php');
+}
+
+if (get_home_page() != HOMEPAGE_SITE) {
+    // Redirect logged-in users to My Moodle overview if required.
+    $redirect = optional_param('redirect', 1, PARAM_BOOL);
+    if (optional_param('setdefaulthome', false, PARAM_BOOL)) {
+        set_user_preference('user_home_page_preference', HOMEPAGE_SITE);
+    } else if (!empty($CFG->defaulthomepage) && ($CFG->defaulthomepage == HOMEPAGE_MY) && $redirect === 1) {
+        redirect($CFG->wwwroot .'/my/');
+    } else if (!empty($CFG->defaulthomepage) && ($CFG->defaulthomepage == HOMEPAGE_USER)) {
+        $PAGE->settingsnav->get('usercurrentsettings')->add(
+            get_string('makethismyhome'),
+            new moodle_url('/', array('setdefaulthome' => true)),
+            navigation_node::TYPE_SETTING);
     }
-    $PAGE->set_url('/', $urlparams);
-    $PAGE->set_course($SITE);
-    $PAGE->set_other_editing_capability('moodle/course:update');
-    $PAGE->set_other_editing_capability('moodle/course:manageactivities');
-    $PAGE->set_other_editing_capability('moodle/course:activityvisibility');
-
-    // Prevent caching of this page to stop confusion when changing page after making AJAX changes
-    $PAGE->set_cacheable(false);
-
-    if ($CFG->forcelogin) {
-        require_login();
-    } else {
-        user_accesstime_log();
+}
+
+$eventparams = array('context' => context_course::instance(SITEID));
+$event = \core\event\course_viewed::create($eventparams);
+$event->trigger();
+
+// If the hub plugin is installed then we let it take over the homepage here.
+if (file_exists($CFG->dirroot.'/local/hub/lib.php') and get_config('local_hub', 'hubenabled')) {
+    require_once($CFG->dirroot.'/local/hub/lib.php');
+    $hub = new local_hub();
+    $continue = $hub->display_homepage();
+    // Function display_homepage() returns true if the hub home page is not displayed
+    // ...mostly when search form is not displayed for not logged users.
+    if (empty($continue)) {
+        exit;
     }
-
-    $hassiteconfig = has_capability('moodle/site:config', context_system::instance());
-
-/// If the site is currently under maintenance, then print a message
-    if (!empty($CFG->maintenance_enabled) and !$hassiteconfig) {
-        print_maintenance_message();
-    }
-
-    if ($hassiteconfig && moodle_needs_upgrading()) {
-        redirect($CFG->wwwroot .'/'. $CFG->admin .'/index.php');
+}
+
+$PAGE->set_pagetype('site-index');
+$PAGE->set_docs_path('');
+$PAGE->set_pagelayout('frontpage');
+$editing = $PAGE->user_is_editing();
+$PAGE->set_title($SITE->fullname);
+$PAGE->set_heading($SITE->fullname);
+$courserenderer = $PAGE->get_renderer('core', 'course');
+echo $OUTPUT->header();
+
+// Print Section or custom info.
+$siteformatoptions = course_get_format($SITE)->get_format_options();
+$modinfo = get_fast_modinfo($SITE);
+$modnames = get_module_types_names();
+$modnamesplural = get_module_types_names(true);
+$modnamesused = $modinfo->get_used_module_names();
+$mods = $modinfo->get_cms();
+
+if (!empty($CFG->customfrontpageinclude)) {
+    include($CFG->customfrontpageinclude);
+
+} else if ($siteformatoptions['numsections'] > 0) {
+    if ($editing) {
+        // Make sure section with number 1 exists.
+        course_create_sections_if_missing($SITE, 1);
+        // Re-request modinfo in case section was created.
+        $modinfo = get_fast_modinfo($SITE);
     }
-
-    if (get_home_page() != HOMEPAGE_SITE) {
-        // Redirect logged-in users to My Moodle overview if required
-        if (optional_param('setdefaulthome', false, PARAM_BOOL)) {
-            set_user_preference('user_home_page_preference', HOMEPAGE_SITE);
-        } else if (!empty($CFG->defaulthomepage) && ($CFG->defaulthomepage == HOMEPAGE_MY) && optional_param('redirect', 1, PARAM_BOOL) === 1) {
-            redirect($CFG->wwwroot .'/my/');
-        } else if (!empty($CFG->defaulthomepage) && ($CFG->defaulthomepage == HOMEPAGE_USER)) {
-            $PAGE->settingsnav->get('usercurrentsettings')->add(get_string('makethismyhome'), new moodle_url('/', array('setdefaulthome'=>true)), navigation_node::TYPE_SETTING);
+    $section = $modinfo->get_section_info(1);
+    if (($section && (!empty($modinfo->sections[1]) or !empty($section->summary))) or $editing) {
+        echo $OUTPUT->box_start('generalbox sitetopic');
+
+        // If currently moving a file then show the current clipboard.
+        if (ismoving($SITE->id)) {
+            $stractivityclipboard = strip_tags(get_string('activityclipboard', '', $USER->activitycopyname));
+            echo '<p><font size="2">';
+            echo "$stractivityclipboard&nbsp;&nbsp;(<a href=\"course/mod.php?cancelcopy=true&amp;sesskey=".sesskey()."\">";
+            echo get_string('cancel') . '</a>)';
+            echo '</font></p>';
         }
-    }
 
-    $eventparams = array('context' => context_course::instance(SITEID));
-    $event = \core\event\course_viewed::create($eventparams);
-    $event->trigger();
-
-/// If the hub plugin is installed then we let it take over the homepage here
-    if (file_exists($CFG->dirroot.'/local/hub/lib.php') and get_config('local_hub', 'hubenabled')) {
-        require_once($CFG->dirroot.'/local/hub/lib.php');
-        $hub = new local_hub();
-        $continue = $hub->display_homepage();
-        //display_homepage() return true if the hub home page is not displayed
-        //mostly when search form is not displayed for not logged users
-        if (empty($continue)) {
-            exit;
-        }
-    }
+        $context = context_course::instance(SITEID);
 
-    $PAGE->set_pagetype('site-index');
-    $PAGE->set_docs_path('');
-    $PAGE->set_pagelayout('frontpage');
-    $editing = $PAGE->user_is_editing();
-    $PAGE->set_title($SITE->fullname);
-    $PAGE->set_heading($SITE->fullname);
-    $courserenderer = $PAGE->get_renderer('core', 'course');
-    echo $OUTPUT->header();
-
-/// Print Section or custom info
-    $siteformatoptions = course_get_format($SITE)->get_format_options();
-    $modinfo = get_fast_modinfo($SITE);
-    $modnames = get_module_types_names();
-    $modnamesplural = get_module_types_names(true);
-    $modnamesused = $modinfo->get_used_module_names();
-    $mods = $modinfo->get_cms();
-
-    if (!empty($CFG->customfrontpageinclude)) {
-        include($CFG->customfrontpageinclude);
-
-    } else if ($siteformatoptions['numsections'] > 0) {
-        if ($editing) {
-            // make sure section with number 1 exists
-            course_create_sections_if_missing($SITE, 1);
-            // re-request modinfo in case section was created
-            $modinfo = get_fast_modinfo($SITE);
+        // If the section name is set we show it.
+        if (!is_null($section->name)) {
+            echo $OUTPUT->heading(
+                format_string($section->name, true, array('context' => $context)),
+                2,
+                'sectionname'
+            );
         }
-        $section = $modinfo->get_section_info(1);
-        if (($section && (!empty($modinfo->sections[1]) or !empty($section->summary))) or $editing) {
-            echo $OUTPUT->box_start('generalbox sitetopic');
-
-            /// If currently moving a file then show the current clipboard
-            if (ismoving($SITE->id)) {
-                $stractivityclipboard = strip_tags(get_string('activityclipboard', '', $USER->activitycopyname));
-                echo '<p><font size="2">';
-                echo "$stractivityclipboard&nbsp;&nbsp;(<a href=\"course/mod.php?cancelcopy=true&amp;sesskey=".sesskey()."\">". get_string('cancel') .'</a>)';
-                echo '</font></p>';
-            }
 
-            $context = context_course::instance(SITEID);
-            $summarytext = file_rewrite_pluginfile_urls($section->summary, 'pluginfile.php', $context->id, 'course', 'section', $section->id);
-            $summaryformatoptions = new stdClass();
-            $summaryformatoptions->noclean = true;
-            $summaryformatoptions->overflowdiv = true;
-
-            echo format_text($summarytext, $section->summaryformat, $summaryformatoptions);
-
-            if ($editing && has_capability('moodle/course:update', $context)) {
-                $streditsummary = get_string('editsummary');
-                echo "<a title=\"$streditsummary\" ".
-                     " href=\"course/editsection.php?id=$section->id\"><img src=\"" . $OUTPUT->pix_url('t/edit') . "\" ".
-                     " class=\"iconsmall\" alt=\"$streditsummary\" /></a><br /><br />";
-            }
+        $summarytext = file_rewrite_pluginfile_urls($section->summary,
+            'pluginfile.php',
+            $context->id,
+            'course',
+            'section',
+            $section->id);
+        $summaryformatoptions = new stdClass();
+        $summaryformatoptions->noclean = true;
+        $summaryformatoptions->overflowdiv = true;
+
+        echo format_text($summarytext, $section->summaryformat, $summaryformatoptions);
+
+        if ($editing && has_capability('moodle/course:update', $context)) {
+            $streditsummary = get_string('editsummary');
+            echo "<a title=\"$streditsummary\" ".
+                 " href=\"course/editsection.php?id=$section->id\"><img src=\"" . $OUTPUT->pix_url('t/edit') . "\" ".
+                 " class=\"iconsmall\" alt=\"$streditsummary\" /></a><br /><br />";
+        }
 
-            $courserenderer = $PAGE->get_renderer('core', 'course');
-            echo $courserenderer->course_section_cm_list($SITE, $section);
+        $courserenderer = $PAGE->get_renderer('core', 'course');
+        echo $courserenderer->course_section_cm_list($SITE, $section);
 
-            echo $courserenderer->course_section_add_cm_control($SITE, $section->section);
-            echo $OUTPUT->box_end();
-        }
+        echo $courserenderer->course_section_add_cm_control($SITE, $section->section);
+        echo $OUTPUT->box_end();
     }
-    // Include course AJAX
-    include_course_ajax($SITE, $modnamesused);
+}
+// Include course AJAX.
+include_course_ajax($SITE, $modnamesused);
+
+if (isloggedin() and !isguestuser() and isset($CFG->frontpageloggedin)) {
+    $frontpagelayout = $CFG->frontpageloggedin;
+} else {
+    $frontpagelayout = $CFG->frontpage;
+}
+
+foreach (explode(',', $frontpagelayout) as $v) {
+    switch ($v) {
+        // Display the main part of the front page.
+        case FRONTPAGENEWS:
+            if ($SITE->newsitems) {
+                // Print forums only when needed.
+                require_once($CFG->dirroot .'/mod/forum/lib.php');
+
+                if (! $newsforum = forum_get_course_forum($SITE->id, 'news')) {
+                    print_error('cannotfindorcreateforum', 'forum');
+                }
 
-    if (isloggedin() and !isguestuser() and isset($CFG->frontpageloggedin)) {
-        $frontpagelayout = $CFG->frontpageloggedin;
-    } else {
-        $frontpagelayout = $CFG->frontpage;
-    }
+                // Fetch news forum context for proper filtering to happen.
+                $newsforumcm = get_coursemodule_from_instance('forum', $newsforum->id, $SITE->id, false, MUST_EXIST);
+                $newsforumcontext = context_module::instance($newsforumcm->id, MUST_EXIST);
 
-    foreach (explode(',',$frontpagelayout) as $v) {
-        switch ($v) {     /// Display the main part of the front page.
-            case FRONTPAGENEWS:
-                if ($SITE->newsitems) { // Print forums only when needed
-                    require_once($CFG->dirroot .'/mod/forum/lib.php');
+                $forumname = format_string($newsforum->name, true, array('context' => $newsforumcontext));
+                echo html_writer::tag('a',
+                    get_string('skipa', 'access', core_text::strtolower(strip_tags($forumname))),
+                    array('href' => '#skipsitenews', 'class' => 'skip-block'));
 
-                    if (! $newsforum = forum_get_course_forum($SITE->id, 'news')) {
-                        print_error('cannotfindorcreateforum', 'forum');
-                    }
+                // Wraps site news forum in div container.
+                echo html_writer::start_tag('div', array('id' => 'site-news-forum'));
 
-                    // fetch news forum context for proper filtering to happen
-                    $newsforumcm = get_coursemodule_from_instance('forum', $newsforum->id, $SITE->id, false, MUST_EXIST);
-                    $newsforumcontext = context_module::instance($newsforumcm->id, MUST_EXIST);
-
-                    $forumname = format_string($newsforum->name, true, array('context' => $newsforumcontext));
-                    echo html_writer::tag('a', get_string('skipa', 'access', core_text::strtolower(strip_tags($forumname))), array('href'=>'#skipsitenews', 'class'=>'skip-block'));
-
-                    // wraps site news forum in div container.
-                    echo html_writer::start_tag('div', array('id'=>'site-news-forum'));
-
-                    if (isloggedin()) {
-                        $SESSION->fromdiscussion = $CFG->wwwroot;
-                        $subtext = '';
-                        if (\mod_forum\subscriptions::is_subscribed($USER->id, $newsforum)) {
-                            if (!\mod_forum\subscriptions::is_forcesubscribed($newsforum)) {
-                                $subtext = get_string('unsubscribe', 'forum');
-                            }
-                        } else {
-                            $subtext = get_string('subscribe', 'forum');
+                if (isloggedin()) {
+                    $SESSION->fromdiscussion = $CFG->wwwroot;
+                    $subtext = '';
+                    if (\mod_forum\subscriptions::is_subscribed($USER->id, $newsforum)) {
+                        if (!\mod_forum\subscriptions::is_forcesubscribed($newsforum)) {
+                            $subtext = get_string('unsubscribe', 'forum');
                         }
-                        echo $OUTPUT->heading($forumname);
-                        $suburl = new moodle_url('/mod/forum/subscribe.php', array('id' => $newsforum->id, 'sesskey' => sesskey()));
-                        echo html_writer::tag('div', html_writer::link($suburl, $subtext), array('class' => 'subscribelink'));
                     } else {
-                        echo $OUTPUT->heading($forumname);
+                        $subtext = get_string('subscribe', 'forum');
                     }
+                    echo $OUTPUT->heading($forumname);
+                    $suburl = new moodle_url('/mod/forum/subscribe.php', array('id' => $newsforum->id, 'sesskey' => sesskey()));
+                    echo html_writer::tag('div', html_writer::link($suburl, $subtext), array('class' => 'subscribelink'));
+                } else {
+                    echo $OUTPUT->heading($forumname);
+                }
 
-                    forum_print_latest_discussions($SITE, $newsforum, $SITE->newsitems, 'plain', 'p.modified DESC');
+                forum_print_latest_discussions($SITE, $newsforum, $SITE->newsitems, 'plain', 'p.modified DESC');
 
-                    //end site news forum div container
-                    echo html_writer::end_tag('div');
+                // End site news forum div container.
+                echo html_writer::end_tag('div');
 
-                    echo html_writer::tag('span', '', array('class'=>'skip-block-to', 'id'=>'skipsitenews'));
-                }
-            break;
+                echo html_writer::tag('span', '', array('class' => 'skip-block-to', 'id' => 'skipsitenews'));
+            }
+        break;
 
-            case FRONTPAGEENROLLEDCOURSELIST:
-                $mycourseshtml = $courserenderer->frontpage_my_courses();
-                if (!empty($mycourseshtml)) {
-                    echo html_writer::tag('a', get_string('skipa', 'access', core_text::strtolower(get_string('mycourses'))), array('href'=>'#skipmycourses', 'class'=>'skip-block'));
+        case FRONTPAGEENROLLEDCOURSELIST:
+            $mycourseshtml = $courserenderer->frontpage_my_courses();
+            if (!empty($mycourseshtml)) {
+                echo html_writer::tag('a',
+                    get_string('skipa', 'access', core_text::strtolower(get_string('mycourses'))),
+                    array('href' => '#skipmycourses', 'class' => 'skip-block'));
 
-                    //wrap frontpage course list in div container
-                    echo html_writer::start_tag('div', array('id'=>'frontpage-course-list'));
+                // Wrap frontpage course list in div container.
+                echo html_writer::start_tag('div', array('id' => 'frontpage-course-list'));
 
-                    echo $OUTPUT->heading(get_string('mycourses'));
-                    echo $mycourseshtml;
+                echo $OUTPUT->heading(get_string('mycourses'));
+                echo $mycourseshtml;
 
-                    //end frontpage course list div container
-                    echo html_writer::end_tag('div');
+                // End frontpage course list div container.
+                echo html_writer::end_tag('div');
 
-                    echo html_writer::tag('span', '', array('class'=>'skip-block-to', 'id'=>'skipmycourses'));
-                    break;
-                }
-                // No "break" here. If there are no enrolled courses - continue to 'Available courses'.
+                echo html_writer::tag('span', '', array('class' => 'skip-block-to', 'id' => 'skipmycourses'));
+                break;
+            }
+            // No "break" here. If there are no enrolled courses - continue to 'Available courses'.
 
-            case FRONTPAGEALLCOURSELIST:
-                $availablecourseshtml = $courserenderer->frontpage_available_courses();
-                if (!empty($availablecourseshtml)) {
-                    echo html_writer::tag('a', get_string('skipa', 'access', core_text::strtolower(get_string('availablecourses'))), array('href'=>'#skipavailablecourses', 'class'=>'skip-block'));
+        case FRONTPAGEALLCOURSELIST:
+            $availablecourseshtml = $courserenderer->frontpage_available_courses();
+            if (!empty($availablecourseshtml)) {
+                echo html_writer::tag('a',
+                    get_string('skipa', 'access', core_text::strtolower(get_string('availablecourses'))),
+                    array('href' => '#skipavailablecourses', 'class' => 'skip-block'));
 
-                    //wrap frontpage course list in div container
-                    echo html_writer::start_tag('div', array('id'=>'frontpage-course-list'));
+                // Wrap frontpage course list in div container.
+                echo html_writer::start_tag('div', array('id' => 'frontpage-course-list'));
 
-                    echo $OUTPUT->heading(get_string('availablecourses'));
-                    echo $availablecourseshtml;
+                echo $OUTPUT->heading(get_string('availablecourses'));
+                echo $availablecourseshtml;
 
-                    //end frontpage course list div container
-                    echo html_writer::end_tag('div');
+                // End frontpage course list div container.
+                echo html_writer::end_tag('div');
 
-                    echo html_writer::tag('span', '', array('class'=>'skip-block-to', 'id'=>'skipavailablecourses'));
-                }
-            break;
+                echo html_writer::tag('span', '', array('class' => 'skip-block-to', 'id' => 'skipavailablecourses'));
+            }
+        break;
 
-            case FRONTPAGECATEGORYNAMES:
-                echo html_writer::tag('a', get_string('skipa', 'access', core_text::strtolower(get_string('categories'))), array('href'=>'#skipcategories', 'class'=>'skip-block'));
+        case FRONTPAGECATEGORYNAMES:
+            echo html_writer::tag('a',
+                get_string('skipa', 'access', core_text::strtolower(get_string('categories'))),
+                array('href' => '#skipcategories', 'class' => 'skip-block'));
 
-                //wrap frontpage category names in div container
-                echo html_writer::start_tag('div', array('id'=>'frontpage-category-names'));
+            // Wrap frontpage category names in div container.
+            echo html_writer::start_tag('div', array('id' => 'frontpage-category-names'));
 
-                echo $OUTPUT->heading(get_string('categories'));
-                echo $courserenderer->frontpage_categories_list();
+            echo $OUTPUT->heading(get_string('categories'));
+            echo $courserenderer->frontpage_categories_list();
 
-                //end frontpage category names div container
-                echo html_writer::end_tag('div');
+            // End frontpage category names div container.
+            echo html_writer::end_tag('div');
 
-                echo html_writer::tag('span', '', array('class'=>'skip-block-to', 'id'=>'skipcategories'));
-            break;
+            echo html_writer::tag('span', '', array('class' => 'skip-block-to', 'id' => 'skipcategories'));
+        break;
 
-            case FRONTPAGECATEGORYCOMBO:
-                echo html_writer::tag('a', get_string('skipa', 'access', core_text::strtolower(get_string('courses'))), array('href'=>'#skipcourses', 'class'=>'skip-block'));
+        case FRONTPAGECATEGORYCOMBO:
+            echo html_writer::tag('a',
+                get_string('skipa', 'access', core_text::strtolower(get_string('courses'))),
+                array('href' => '#skipcourses', 'class' => 'skip-block'));
 
-                //wrap frontpage category combo in div container
-                echo html_writer::start_tag('div', array('id'=>'frontpage-category-combo'));
+            // Wrap frontpage category combo in div container.
+            echo html_writer::start_tag('div', array('id' => 'frontpage-category-combo'));
 
-                echo $OUTPUT->heading(get_string('courses'));
-                echo $courserenderer->frontpage_combo_list();
+            echo $OUTPUT->heading(get_string('courses'));
+            echo $courserenderer->frontpage_combo_list();
 
-                //end frontpage category combo div container
-                echo html_writer::end_tag('div');
+            // End frontpage category combo div container.
+            echo html_writer::end_tag('div');
 
-                echo html_writer::tag('span', '', array('class'=>'skip-block-to', 'id'=>'skipcourses'));
-            break;
+            echo html_writer::tag('span', '', array('class' => 'skip-block-to', 'id' => 'skipcourses'));
+        break;
 
-            case FRONTPAGECOURSESEARCH:
-                echo $OUTPUT->box($courserenderer->course_search_form('', 'short'), 'mdl-align');
-            break;
+        case FRONTPAGECOURSESEARCH:
+            echo $OUTPUT->box($courserenderer->course_search_form('', 'short'), 'mdl-align');
+        break;
 
-        }
-        echo '<br />';
-    }
-    if ($editing && has_capability('moodle/course:create', context_system::instance())) {
-        echo $courserenderer->add_new_course_button();
     }
-    echo $OUTPUT->footer();
+    echo '<br />';
+}
+if ($editing && has_capability('moodle/course:create', context_system::instance())) {
+    echo $courserenderer->add_new_course_button();
+}
+echo $OUTPUT->footer();
index f94de6f..e4948d5 100644 (file)
@@ -44,7 +44,8 @@ $string['dmlexceptiononinstall'] = '<p>Se ha producido un error de base de datos
 $string['downloadedfilecheckfailed'] = 'Ha fallado la comprobación del archivo descargado';
 $string['invalidmd5'] = 'La variable de verificación MD5 es incorrecta no es valida - trate nuevamente';
 $string['missingrequiredfield'] = 'Falta algún campo necesario';
-$string['remotedownloaderror'] = 'Falló la descarga del componente a su servidor. Se recomienda verificar los ajustes del proxy, extensión PHP cURL.<br /><br />Debe descargar el<a href="{$a->url}">{$a->url}</a> archivo manualmente, copiarlo en "{$a->dest}" en su servidor y descomprimirlo allí.';
+$string['remotedownloaderror'] = '<p>Falló la descarga del componente a su servidor. Se recomienda verificar los ajustes del proxy, extensión PHP cURL.</p>
+<p>Debe descargar el <a href="{$a->url}">{$a->url}</a> archivo manualmente, copiarlo en "{$a->dest}" en su servidor y descomprimirlo allí.</p>';
 $string['wrongdestpath'] = 'Ruta de destino errónea.';
 $string['wrongsourcebase'] = 'Base de fuente de URL errónea.';
 $string['wrongzipfilename'] = 'Nombre de archivo ZIP erróneo.';
index 93ea1f7..7170e30 100644 (file)
@@ -34,8 +34,8 @@ $string['admindirname'] = 'Directorio Admin';
 $string['availablelangs'] = 'Lista de idiomas disponibles';
 $string['chooselanguagehead'] = 'Seleccionar idioma';
 $string['chooselanguagesub'] = 'Por favor, seleccione un idioma para el proceso de instalación. Este idioma se usará también como idioma por defecto del sitio, si bien puede cambiarse más adelante.';
-$string['clialreadyconfigured'] = 'El archivo config.php ya existe. Por favor, utilice admin/cli/upgrade.php si desea actualizar su sitio web.';
-$string['clialreadyinstalled'] = 'El archivo config.php ya existe. Por favor, utilice admin/cli/upgrade.php si desea actualizar su sitio web.';
+$string['clialreadyconfigured'] = 'El archivo de configuración config.php ya existe. Por favor, utilice admin/cli/install_database.php para instalar Moodle en este sitio.';
+$string['clialreadyinstalled'] = 'El archivo de configuración config.php ya existe. Por favor, utilice admin/cli/install_database.php para actualizar el Moodle en este sitio.';
 $string['cliinstallheader'] = 'Programa de instalación Moodle de línea de comando {$a}';
 $string['databasehost'] = 'Servidor de la base de datos';
 $string['databasename'] = 'Nombre de la base de datos';
@@ -43,13 +43,13 @@ $string['databasetypehead'] = 'Seleccione el controlador de la base de datos';
 $string['dataroot'] = 'Directorio de Datos';
 $string['datarootpermission'] = 'Permiso directorios de datos';
 $string['dbprefix'] = 'Prefijo de tablas';
-$string['dirroot'] = 'Directorio Moodle';
+$string['dirroot'] = 'Directorio de Moodle';
 $string['environmenthead'] = 'Comprobando su entorno';
 $string['environmentsub2'] = 'Cada versión de Moodle tiene algún requisito mínimo de la versión de PHP y un número obligatorio de extensiones de PHP.
 Una comprobación del entorno completo se realiza antes de cada instalación y actualización. Por favor, póngase en contacto con el administrador del servidor si no sabes cómo instalar la nueva versión o habilitar las extensiones PHP.';
 $string['errorsinenvironment'] = 'La comprobación del entorno fallo!';
 $string['installation'] = 'Instalación';
-$string['langdownloaderror'] = 'El idioma "{$a}" no pudo ser instalado. El proceso de instalación continuará en inglés.';
+$string['langdownloaderror'] = 'El idioma "{$a}" no pudo ser descargado. El proceso de instalación continuará en Inglés.';
 $string['memorylimithelp'] = '<p>El límite de memoria PHP en su servidor es actualmente {$a}.</p>
 
 <p>Esto puede ocasionar que Moodle tenga problemas de memoria más adelante, especialmente si usted tiene activados muchos módulos y/o muchos usuarios.</p>
index d14187b..03a1b99 100644 (file)
 
 defined('MOODLE_INTERNAL') || die();
 
+$string['clianswerno'] = 'n';
+$string['cliansweryes'] = 'y';
+$string['cliincorrectvalueerror'] = 'Грешка, погрешна вредност "{$a->value}"
+за "{$a->option}"';
+$string['cliincorrectvalueretry'] = 'Неточни вредност, ве молиме обидете се повторно';
+$string['clitypevalue'] = 'впишете вредност';
+$string['clitypevaluedefault'] = 'впишете вредност, притиснете Enter за да го користите стандардната вредност ({$a})';
+$string['cliyesnoprompt'] = 'впишете y (значи да) или n (значи не)';
 $string['environmentrequireinstall'] = 'потребно е да се инсталира/овозможи';
 $string['environmentrequireversion'] = 'верзијата {$a->needed} е потребна, а вие извршувате {$a->current}';
index 082fa47..6e7352b 100644 (file)
@@ -27,6 +27,7 @@ $string['activaterep'] = 'Active repositories';
 $string['activerepository'] = 'Available repository plugins';
 $string['add'] = 'Add';
 $string['addfile'] = 'Add...';
+$string['addfiletext'] = 'Add file';
 $string['addplugin'] = 'Add a repository plugin';
 $string['allowexternallinks'] = 'Allow external links';
 $string['areamainfile'] = 'Main file';
@@ -87,10 +88,14 @@ $string['deleterepository'] = 'Delete this repository';
 $string['detailview'] = 'View details';
 $string['dimensions'] = 'Dimensions';
 $string['disabled'] = 'Disabled';
+$string['displayasdetails'] = 'Display as file details';
+$string['displayasicons'] = 'Display as file icons';
+$string['displayastree'] = 'Display as file tree';
 $string['displaydetails'] = 'Display folder with file details';
 $string['displayicons'] = 'Display folder with file icons';
 $string['displaytree'] = 'Display folder as file tree';
 $string['download'] = 'Download';
+$string['downloadallfiles'] = 'Download all files';
 $string['downloadfolder'] = 'Download all';
 $string['downloadsucc'] = 'The file has been downloaded successfully';
 $string['draftareanofiles'] = 'Cannot be downloaded because there is no files attached';
@@ -128,6 +133,7 @@ $string['getfiletimeout'] = 'Get file timeout';
 $string['help'] = 'Help';
 $string['choosealink'] = 'Choose a link...';
 $string['chooselicense'] = 'Choose license';
+$string['createfolder'] = 'Create folder';
 $string['iconview'] = 'View as icons';
 $string['imagesize'] = '{$a->width} x {$a->height} px';
 $string['instance'] = 'instance';
index e38b2db..613b7a9 100644 (file)
@@ -43,6 +43,7 @@ class behat_selectors {
         'block' => 'block',
         'region' => 'region',
         'table_row' => 'table_row',
+        'list_item' => 'list_item',
         'table' => 'table',
         'fieldset' => 'fieldset',
         'css_element' => 'css_element',
@@ -57,6 +58,7 @@ class behat_selectors {
         'block' => 'block',
         'region' => 'region',
         'table_row' => 'table_row',
+        'list_item' => 'list_item',
         'link' => 'link',
         'button' => 'button',
         'link_or_button' => 'link_or_button',
@@ -106,6 +108,9 @@ XPATH
 XPATH
         , 'table_row' => <<<XPATH
 .//tr[contains(normalize-space(.), %locator%)]
+XPATH
+        , 'list_item' => <<<XPATH
+.//li[contains(normalize-space(.), %locator%)]
 XPATH
         , 'filemanager' => <<<XPATH
 //div[contains(concat(' ', normalize-space(@class), ' '), ' ffilemanager ')]
index 713bb9f..ee1f112 100644 (file)
@@ -294,6 +294,12 @@ class behat_util extends testing_util {
      * Reset contents of all database tables to initial values, reset caches, etc.
      */
     public static function reset_all_data() {
+        // Reset database.
+        self::reset_database();
+
+        // Purge dataroot directory.
+        self::reset_dataroot();
+
         // Reset all static caches.
         accesslib_clear_all_caches(true);
         // Reset the nasty strings list used during the last test.
@@ -310,11 +316,5 @@ class behat_util extends testing_util {
 
         // Inform data generator.
         self::get_data_generator()->reset();
-
-        // Purge dataroot directory.
-        self::reset_dataroot();
-
-        // Reset database.
-        self::reset_database();
     }
 }
index f7a5868..2c6a5c0 100644 (file)
@@ -34,7 +34,7 @@ class theme extends base {
     public function is_uninstall_allowed() {
         global $CFG;
 
-        if ($this->name === 'standard' or $this->name === 'base' or $this->name === 'bootstrapbase') {
+        if ($this->name === 'base' or $this->name === 'bootstrapbase') {
             // All of these are protected for now.
             return false;
         }
index 0d461c2..8dfc9ce 100644 (file)
@@ -847,4 +847,41 @@ class manager {
         \core\session\manager::set_user($user);
         $event->trigger();
     }
+
+    /**
+     * Add a JS session keepalive to the page.
+     *
+     * A JS session keepalive script will be called to update the session modification time every $frequency seconds.
+     *
+     * Upon failure, the specified error message will be shown to the user.
+     *
+     * @param string $identifier The string identifier for the message to show on failure.
+     * @param string $component The string component for the message to show on failure.
+     * @param int $frequency The update frequency in seconds.
+     * @throws coding_exception IF the frequency is longer than the session lifetime.
+     */
+    public static function keepalive($identifier = 'sessionerroruser', $component = 'error', $frequency = null) {
+        global $CFG, $PAGE;
+
+        if ($frequency) {
+            if ($frequency > $CFG->sessiontimeout) {
+                // Sanity check the frequency.
+                throw new \coding_exception('Keepalive frequency is longer than the session lifespan.');
+            }
+        } else {
+            // A frequency of sessiontimeout / 3 allows for one missed request whilst still preserving the session.
+            $frequency = $CFG->sessiontimeout / 3;
+        }
+
+        // Add the session keepalive script to the list of page output requirements.
+        $sessionkeepaliveurl = new \moodle_url('/lib/sessionkeepalive_ajax.php');
+        $PAGE->requires->string_for_js($identifier, $component);
+        $PAGE->requires->yui_module('moodle-core-checknet', 'M.core.checknet.init', array(array(
+            // The JS config takes this is milliseconds rather than seconds.
+            'frequency' => $frequency * 1000,
+            'message' => array($identifier, $component),
+            'uri' => $sessionkeepaliveurl->out(),
+        )));
+    }
+
 }
index 499cfee..ebd3b84 100644 (file)
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
 /**
- * Used to be used for tracking conditions that apply before activities are
- * displayed to students ('conditional availability').
- *
- * Now replaced by the availability API. This library is a stub; some functions
- * still work while others throw exceptions. New code should not rely on the
- * classes, functions, or constants defined here.
+ * DO NOT INCLUDE THIS FILE.
  *
  * @package core_availability
  * @copyright 2014 The Open University
 
 defined('MOODLE_INTERNAL') || die();
 
-/**
- * CONDITION_STUDENTVIEW_HIDE - The activity is not displayed to students at all when conditions aren't met.
- */
-define('CONDITION_STUDENTVIEW_HIDE', 0);
-/**
- * CONDITION_STUDENTVIEW_SHOW - The activity is displayed to students as a greyed-out name, with
- * informational text that explains the conditions under which it will be available.
- */
-define('CONDITION_STUDENTVIEW_SHOW', 1);
-
-/**
- * CONDITION_MISSING_NOTHING - The $item variable is expected to contain all completion-related data
- */
-define('CONDITION_MISSING_NOTHING', 0);
-/**
- * CONDITION_MISSING_EXTRATABLE - The $item variable is expected to contain the fields from
- * the relevant table (course_modules or course_sections) but not the _availability data
- */
-define('CONDITION_MISSING_EXTRATABLE', 1);
-/**
- * CONDITION_MISSING_EVERYTHING - The $item variable is expected to contain nothing except the ID
- */
-define('CONDITION_MISSING_EVERYTHING', 2);
-
-/**
- * OP_CONTAINS - comparison operator that determines whether a specified user field contains
- * a provided variable
- */
-define('OP_CONTAINS', 'contains');
-/**
- * OP_DOES_NOT_CONTAIN - comparison operator that determines whether a specified user field does not
- * contain a provided variable
- */
-define('OP_DOES_NOT_CONTAIN', 'doesnotcontain');
-/**
- * OP_IS_EQUAL_TO - comparison operator that determines whether a specified user field is equal to
- * a provided variable
- */
-define('OP_IS_EQUAL_TO', 'isequalto');
-/**
- * OP_STARTS_WITH - comparison operator that determines whether a specified user field starts with
- * a provided variable
- */
-define('OP_STARTS_WITH', 'startswith');
-/**
- * OP_ENDS_WITH - comparison operator that determines whether a specified user field ends with
- * a provided variable
- */
-define('OP_ENDS_WITH', 'endswith');
-/**
- * OP_IS_EMPTY - comparison operator that determines whether a specified user field is empty
- */
-define('OP_IS_EMPTY', 'isempty');
-/**
- * OP_IS_NOT_EMPTY - comparison operator that determines whether a specified user field is not empty
- */
-define('OP_IS_NOT_EMPTY', 'isnotempty');
-
-require_once($CFG->libdir.'/completionlib.php');
-
-/**
- * Core class to handle conditional activities.
- *
- * This class is now deprecated and partially functional. Public functions either
- * work and output deprecated messages or (in the case of the more obscure ones
- * which weren't really for public use, or those which can't be implemented in
- * the new API) throw exceptions.
- *
- * @copyright 2014 The Open University
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- * @deprecated Since Moodle 2.7
- */
-class condition_info extends condition_info_base {
-    /**
-     * Constructs with course-module details.
-     *
-     * @global moodle_database $DB
-     * @uses CONDITION_MISSING_NOTHING
-     * @param object $cm Moodle course-module object. Required ->id, ->course
-     *   will save time, using a full cm_info will save more time
-     * @param int $expectingmissing Used to control whether or not a developer
-     *   debugging message (performance warning) will be displayed if some of
-     *   the above data is missing and needs to be retrieved; a
-     *   CONDITION_MISSING_xx constant
-     * @param bool $loaddata If you need a 'write-only' object, set this value
-     *   to false to prevent database access from constructor
-     * @deprecated Since Moodle 2.7
-     */
-    public function __construct($cm, $expectingmissing = CONDITION_MISSING_NOTHING,
-            $loaddata=true) {
-        global $DB;
-        debugging('The condition_info class is deprecated; change to \core_availability\info_module',
-                DEBUG_DEVELOPER);
-
-        // Check ID as otherwise we can't do the other queries.
-        if (empty($cm->id)) {
-            throw new coding_exception('Invalid parameters; item ID not included');
-        }
-
-        // Load cm_info object.
-        if (!($cm instanceof cm_info)) {
-            // Get modinfo.
-            if (empty($cm->course)) {
-                $modinfo = get_fast_modinfo(
-                        $DB->get_field('course_modules', 'course', array('id' => $cm->id), MUST_EXIST));
-            } else {
-                $modinfo = get_fast_modinfo($cm->course);
-            }
-
-            // Get $cm object.
-            $cm = $modinfo->get_cm($cm->id);
-        }
-
-        $this->item = $cm;
-    }
-
-    /**
-     * Adds the extra availability conditions (if any) into the given
-     * course-module (or section) object.
-     *
-     * This function may be called statically (for the editing form) or
-     * dynamically.
-     *
-     * @param object $cm Moodle course-module data object
-     * @deprecated Since Moodle 2.7 (does nothing)
-     */
-    public static function fill_availability_conditions($cm) {
-        debugging('Calls to condition_info::fill_availability_conditions should be removed',
-                DEBUG_DEVELOPER);
-    }
-
-    /**
-     * Gets the course-module object with full necessary data to determine availability.
-     *
-     * @return object Course-module object with full data
-     * @deprecated Since Moodle 2.7
-     */
-    public function get_full_course_module() {
-        debugging('Calls to condition_info::get_full_course_module should be removed',
-                DEBUG_DEVELOPER);
-        return $this->item;
-    }
-
-    /**
-     * Used to update a table (which no longer exists) based on form data
-     * (which is no longer used).
-     *
-     * Should only have been called from core code. Now removed (throws exception).
-     *
-     * @param object $cm Course-module with as much data as necessary, min id
-     * @param object $fromform Data from form
-     * @param bool $wipefirst If true, wipes existing conditions
-     * @deprecated Since Moodle 2.7 (not available)
-     * @throws Always throws a coding_exception
-     */
-    public static function update_cm_from_form($cm, $fromform, $wipefirst=true) {
-        throw new coding_exception('Function no longer available');
-    }
-
-    /**
-     * Used to be used in course/lib.php because we needed to disable the
-     * completion JS if a completion value affects a conditional activity.
-     *
-     * Should only have been called from core code. Now removed (throws exception).
-     *
-     * @global stdClass $CONDITIONLIB_PRIVATE
-     * @param object $course Moodle course object
-     * @param object $item Moodle course-module
-     * @deprecated Since Moodle 2.7 (not available)
-     * @throws Always throws a coding_exception
-     */
-    public static function completion_value_used_as_condition($course, $cm) {
-        throw new coding_exception('Function no longer available');
-    }
-}
-
-
-/**
- * Handles conditional access to sections.
- *
- * This class is now deprecated and partially functional. Public functions either
- * work and output deprecated messages or (in the case of the more obscure ones
- * which weren't really for public use, or those which can't be implemented in
- * the new API) throw exceptions.
- *
- * @copyright 2014 The Open University
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- * @deprecated Since Moodle 2.7
- */
-class condition_info_section extends condition_info_base {
-    /**
-     * Constructs with course-module details.
-     *
-     * @global moodle_database $DB
-     * @uses CONDITION_MISSING_NOTHING
-     * @param object $section Moodle section object. Required ->id, ->course
-     *   will save time, using a full section_info will save more time
-     * @param int $expectingmissing Used to control whether or not a developer
-     *   debugging message (performance warning) will be displayed if some of
-     *   the above data is missing and needs to be retrieved; a
-     *   CONDITION_MISSING_xx constant
-     * @param bool $loaddata If you need a 'write-only' object, set this value
-     *   to false to prevent database access from constructor
-     * @deprecated Since Moodle 2.7
-     */
-    public function __construct($section, $expectingmissing = CONDITION_MISSING_NOTHING,
-            $loaddata=true) {
-        global $DB;
-        debugging('The condition_info_section class is deprecated; change to \core_availability\info_section',
-                DEBUG_DEVELOPER);
-
-        // Check ID as otherwise we can't do the other queries.
-        if (empty($section->id)) {
-            throw new coding_exception('Invalid parameters; item ID not included');
-        }
-
-        // Load cm_info object.
-        if (!($section instanceof section_info)) {
-            // Get modinfo.
-            if (empty($section->course)) {
-                $modinfo = get_fast_modinfo(
-                        $DB->get_field('course_sections', 'course', array('id' => $section->id), MUST_EXIST));
-            } else {
-                $modinfo = get_fast_modinfo($section->course);
-            }
-
-            // Get $cm object.
-            foreach ($modinfo->get_section_info_all() as $possible) {
-                if ($possible->id === $section->id) {
-                    $section = $possible;
-                    break;
-                }
-            }
-        }
-
-        $this->item = $section;
-    }
-
-    /**
-     * Adds the extra availability conditions (if any) into the given
-     * course-module (or section) object.
-     *
-     * This function may be called statically (for the editing form) or
-     * dynamically.
-     *
-     * @param object $section Moodle section data object
-     * @deprecated Since Moodle 2.7 (does nothing)
-     */
-    public static function fill_availability_conditions($section) {
-        debugging('Calls to condition_info_section::fill_availability_conditions should be removed',
-                DEBUG_DEVELOPER);
-    }
-
-    /**
-     * Gets the section object with full necessary data to determine availability.
-     *
-     * @return section_info Section object with full data
-     * @deprecated Since Moodle 2.7
-     */
-    public function get_full_section() {
-        debugging('Calls to condition_info_section::get_full_section should be removed',
-                DEBUG_DEVELOPER);
-        return $this->item;
-    }
-
-    /**
-     * Utility function that used to be called by modedit.php; updated a
-     * table (that no longer exists) based on the module form data.
-     *
-     * Should only have been called from core code. Now removed (throws exception).
-     *
-     * @param object $section Section object, must at minimum contain id
-     * @param object $fromform Data from form
-     * @param bool $wipefirst If true, wipes existing conditions
-     * @deprecated Since Moodle 2.7 (not available)
-     * @throws Always throws a coding_exception
-     */
-    public static function update_section_from_form($section, $fromform, $wipefirst=true) {
-        throw new coding_exception('Function no longer available');
-    }
-}
-
-
-/**
- * Base class to handle conditional items (course_modules or sections).
- *
- * This class is now deprecated and partially functional. Public functions either
- * work and output deprecated messages or (in the case of the more obscure ones
- * which weren't really for public use, or those which can't be implemented in
- * the new API) throw exceptions.
- *
- * @copyright 2012 The Open University
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- * @deprecated Since Moodle 2.7
- */
-abstract class condition_info_base {
-    /** @var cm_info|section_info Item with availability data */
-    protected $item;
-
-    /**
-     * The operators that provide the relationship
-     * between a field and a value.
-     *
-     * @deprecated Since Moodle 2.7 (not available)
-     * @throws Always throws a coding_exception
-     */
-    public static function get_condition_user_field_operators() {
-        throw new coding_exception('Function no longer available');
-    }
-
-    /**
-     * Returns list of user fields that can be compared.
-     *
-     * If you specify $formatoptions, then format_string will be called on the
-     * custom field names. This is necessary for multilang support to work so
-     * you should include this parameter unless you are going to format the
-     * text later.
-     *
-     * @param array $formatoptions Passed to format_string if provided
-     * @deprecated Since Moodle 2.7 (not available)
-     * @throws Always throws a coding_exception
-     */
-    public static function get_condition_user_fields($formatoptions = null) {
-        throw new coding_exception('Function no longer available');
-    }
-
-    /**
-     * Adds to the database a condition based on completion of another module.
-     *
-     * Should only have been called from core and test code. Now removed
-     * (throws exception).
-     *
-     * @global moodle_database $DB
-     * @param int $cmid ID of other module
-     * @param int $requiredcompletion COMPLETION_xx constant
-     * @deprecated Since Moodle 2.7 (not available)
-     * @throws Always throws a coding_exception
-     */
-    public function add_completion_condition($cmid, $requiredcompletion) {
-        throw new coding_exception('Function no longer available');
-    }
-
-    /**
-     * Adds user fields condition
-     *
-     * Should only have been called from core and test code. Now removed
-     * (throws exception).
-     *
-     * @param mixed $field numeric if it is a user profile field, character
-     *                     if it is a column in the user table
-     * @param int $operator specifies the relationship between field and value
-     * @param char $value the value of the field
-     * @deprecated Since Moodle 2.7 (not available)
-     * @throws Always throws a coding_exception
-     */
-    public function add_user_field_condition($field, $operator, $value) {
-        throw new coding_exception('Function no longer available');
-    }
-
-    /**
-     * Adds to the database a condition based on the value of a grade item.
-     *
-     * Should only have been called from core and test code. Now removed
-     * (throws exception).
-     *
-     * @global moodle_database $DB
-     * @param int $gradeitemid ID of grade item
-     * @param float $min Minimum grade (>=), up to 5 decimal points, or null if none
-     * @param float $max Maximum grade (<), up to 5 decimal points, or null if none
-     * @param bool $updateinmemory If true, updates data in memory; otherwise,
-     *   memory version may be out of date (this has performance consequences,
-     *   so don't do it unless it really needs updating)
-     * @deprecated Since Moodle 2.7 (not available)
-     * @throws Always throws a coding_exception
-     */
-    public function add_grade_condition($gradeitemid, $min, $max, $updateinmemory=false) {
-        throw new coding_exception('Function no longer available');
-    }
-
-    /**
-     * Erases from the database all conditions for this activity.
-     *
-     * Should only have been called from core and test code. Now removed
-     * (throws exception).
-     *
-     * @deprecated Since Moodle 2.7 (not available)
-     * @throws Always throws a coding_exception
-     */
-    public function wipe_conditions() {
-        throw new coding_exception('Function no longer available');
-    }
-
-    /**
-     * Integration point with new API; obtains the new class for this item.
-     *
-     * @return \core_availability\info Availability info for item
-     */
-    protected function get_availability_info() {
-        if ($this->item instanceof section_info) {
-            return new \core_availability\info_section($this->item);
-        } else {
-            return new \core_availability\info_module($this->item);
-        }
-    }
-
-    /**
-     * Obtains a string describing all availability restrictions (even if
-     * they do not apply any more).
-     *
-     * @param course_modinfo|null $modinfo Usually leave as null for default. Specify when
-     *   calling recursively from inside get_fast_modinfo()
-     * @return string Information string (for admin) about all restrictions on
-     *   this item
-     * @deprecated Since Moodle 2.7
-     */
-    public function get_full_information($modinfo=null) {
-        debugging('condition_info*::get_full_information() is deprecated, replace ' .
-                'with new \core_availability\info_module($cm)->get_full_information()',
-                DEBUG_DEVELOPER);
-        return $this->get_availability_info()->get_full_information($modinfo);
-    }
-
-    /**
-     * Determines whether this particular item is currently available
-     * according to these criteria.
-     *
-     * - This does not include the 'visible' setting (i.e. this might return
-     *   true even if visible is false); visible is handled independently.
-     * - This does not take account of the viewhiddenactivities capability.
-     *   That should apply later.
-     *
-     * @uses COMPLETION_COMPLETE
-     * @uses COMPLETION_COMPLETE_FAIL
-     * @uses COMPLETION_COMPLETE_PASS
-     * @param string $information If the item has availability restrictions,
-     *   a string that describes the conditions will be stored in this variable;
-     *   if this variable is set blank, that means don't display anything
-     * @param bool $grabthelot Performance hint: if true, caches information
-     *   required for all course-modules, to make the front page and similar
-     *   pages work more quickly (works only for current user)
-     * @param int $userid If set, specifies a different user ID to check availability for
-     * @param course_modinfo|null $modinfo Usually leave as null for default. Specify when
-     *   calling recursively from inside get_fast_modinfo()
-     * @return bool True if this item is available to the user, false otherwise
-     * @deprecated Since Moodle 2.7
-     */
-    public function is_available(&$information, $grabthelot=false, $userid=0, $modinfo=null) {
-        debugging('condition_info*::is_available() is deprecated, replace ' .
-                'with new \core_availability\info_module($cm)->is_available()',
-                DEBUG_DEVELOPER);
-        return $this->get_availability_info()->is_available(
-                $information, $grabthelot, $userid, $modinfo);
-    }
-
-    /**
-     * Checks whether availability information should be shown to normal users.
-     *
-     * This information no longer makes sense with the new system because there
-     * are multiple show options. (I doubt anyone much used this function anyhow!)
-     *
-     * @return bool True if information about availability should be shown to
-     *   normal users
-     * @deprecated Since Moodle 2.7
-     */
-    public function show_availability() {
-        debugging('condition_info*::show_availability() is deprecated and there ' .
-                'is no direct replacement (this is no longer a boolean value), ' .
-                'please refactor code',
-                DEBUG_DEVELOPER);
-        return false;
-    }
-
-    /**
-     * Used to be called by grade code to inform the completion system when a
-     * grade was changed.
-     *
-     * This function should not have ever been used outside the grade API, so
-     * it now just throws an exception.
-     *
-     * @param grade_grade $grade
-     * @param bool $deleted
-     * @deprecated Since Moodle 2.7 (not available)
-     * @throws Always throws a coding_exception
-     */
-    public static function inform_grade_changed($grade, $deleted) {
-        throw new coding_exception('Function no longer available');
-    }
-
-    /**
-     * Used to be used for testing.
-     *
-     * @deprecated since 2.6
-     */
-    public static function wipe_session_cache() {
-        debugging('Calls to completion_info::wipe_session_cache should be removed', DEBUG_DEVELOPER);
-    }
-
-    /**
-     * Initialises the global cache
-     *
-     * @deprecated Since Moodle 2.7
-     */
-    public static function init_global_cache() {
-        debugging('Calls to completion_info::init_globa_cache should be removed', DEBUG_DEVELOPER);
-    }
-}
+throw new coding_exception('condition_info,condition_info_section,condition_info_base classes can not be used any more,
+        please use respective classes from \core_availability namespace');
\ No newline at end of file
index fca8414..a027c55 100644 (file)
@@ -556,44 +556,6 @@ class coursecat implements renderable, cacheable_object, IteratorAggregate {
                 has_capability('moodle/category:viewhiddencategories', $this->get_context());
     }
 
-    /**
-     * Returns all categories visible to the current user
-     *
-     * This is a generic function that returns an array of
-     * (category id => coursecat object) sorted by sortorder
-     *
-     * @see coursecat::get_children()
-     *
-     * @return cacheable_object_array array of coursecat objects
-     */
-    public static function get_all_visible() {
-        global $USER;
-        $coursecatcache = cache::make('core', 'coursecat');
-        $ids = $coursecatcache->get('user'. $USER->id);
-        if ($ids === false) {
-            $all = self::get_all_ids();
-            $parentvisible = $all[0];
-            $rv = array();
-            foreach ($all as $id => $children) {
-                if ($id && in_array($id, $parentvisible) &&
-                        ($coursecat = self::get($id, IGNORE_MISSING)) &&
-                        (!$coursecat->parent || isset($rv[$coursecat->parent]))) {
-                    $rv[$id] = $coursecat;
-                    $parentvisible += $children;
-                }
-            }
-            $coursecatcache->set('user'. $USER->id, array_keys($rv));
-        } else {
-            $rv = array();
-            foreach ($ids as $id) {
-                if ($coursecat = self::get($id, IGNORE_MISSING)) {
-                    $rv[$id] = $coursecat;
-                }
-            }
-        }
-        return $rv;
-    }
-
     /**
      * Returns the complete corresponding record from DB table course_categories
      *
index 987e5c2..78917c1 100644 (file)
@@ -3250,16 +3250,10 @@ function get_context_instance($contextlevel, $instance = 0, $strictness = IGNORE
  * Get a context instance as an object, from a given context id.
  *
  * @deprecated since Moodle 2.2 MDL-35009 - please do not use this function any more.
- * @todo MDL-34550 This will be deleted in Moodle 2.8
  * @see context::instance_by_id($id)
- * @param int $id context id
- * @param int $strictness IGNORE_MISSING means compatible mode, false returned if record not found, debug message if more found;
- *                        MUST_EXIST means throw exception if no record or multiple records found
- * @return context|bool the context object or false if not found.
  */
 function get_context_instance_by_id($id, $strictness = IGNORE_MISSING) {
-    debugging('get_context_instance_by_id() is deprecated, please use context::instance_by_id($id) instead.', DEBUG_DEVELOPER);
-    return context::instance_by_id($id, $strictness);
+    throw new coding_exception('get_context_instance_by_id() is now removed, please use context::instance_by_id($id) instead.');
 }
 
 /**
@@ -3891,75 +3885,31 @@ function can_use_html_editor() {
 
 
 /**
- * Returns an object with counts of failed login attempts
- *
- * Returns information about failed login attempts.  If the current user is
- * an admin, then two numbers are returned:  the number of attempts and the
- * number of accounts.  For non-admins, only the attempts on the given user
- * are shown.
+ * Returns an object with counts of failed login attempts.
  *
- * @deprecate since Moodle 2.7, use {@link user_count_login_failures()} instead.
- * @global moodle_database $DB
- * @uses CONTEXT_SYSTEM
- * @param string $mode Either 'admin' or 'everybody'
- * @param string $username The username we are searching for
- * @param string $lastlogin The date from which we are searching
- * @return int
+ * @deprecated since Moodle 2.7, use {@link user_count_login_failures()} instead.
  */
 function count_login_failures($mode, $username, $lastlogin) {
-    global $DB;
-
-    debugging('This method has been deprecated. Please use user_count_login_failures() instead.', DEBUG_DEVELOPER);
-
-    $params = array('mode'=>$mode, 'username'=>$username, 'lastlogin'=>$lastlogin);
-    $select = "module='login' AND action='error' AND time > :lastlogin";
-
-    $count = new stdClass();
-
-    if (is_siteadmin()) {
-        if ($count->attempts = $DB->count_records_select('log', $select, $params)) {
-            $count->accounts = $DB->count_records_select('log', $select, $params, 'COUNT(DISTINCT info)');
-            return $count;
-        }
-    } else if ($mode == 'everybody') {
-        if ($count->attempts = $DB->count_records_select('log', "$select AND info = :username", $params)) {
-            return $count;
-        }
-    }
-    return NULL;
+    throw new coding_exception('count_login_failures() can not be used any more, please use user_count_login_failures().');
 }
 
 /**
- * Returns whether ajax is enabled/allowed or not.
- * This function is deprecated and always returns true.
+ * It should no longer be required to work without JavaScript enabled.
  *
- * @param array $unused - not used any more.
- * @return bool
- * @deprecated since 2.7 MDL-33099 - please do not use this function any more.
- * @todo MDL-44088 This will be removed in Moodle 2.9.
+ * @deprecated since 2.7 MDL-33099/MDL-44088 - please do not use this function any more.
  */
 function ajaxenabled(array $browsers = null) {
-    debugging('ajaxenabled() is deprecated - please update your code to assume it returns true.', DEBUG_DEVELOPER);
-    return true;
+    throw new coding_exception('ajaxenabled() can not be used anymore. Update your code to work with JS at all times.');
 }
 
 /**
- * Determine whether a course module is visible within a course,
- * this is different from instance_is_visible() - faster and visibility for user
+ * Determine whether a course module is visible within a course.
  *
- * @global object
- * @global object
- * @uses DEBUG_DEVELOPER
- * @uses CONTEXT_MODULE
- * @param object $cm object
- * @param int $userid empty means current user
- * @return bool Success
- * @deprecated Since Moodle 2.7
+ * @deprecated Since Moodle 2.7 MDL-44070
  */
 function coursemodule_visible_for_user($cm, $userid=0) {
-    debugging('coursemodule_visible_for_user() deprecated since Moodle 2.7. ' .
-            'Replace with \core_availability\info_module::is_user_visible().');
-    return \core_availability\info_module::is_user_visible($cm, $userid, false);
+    throw new coding_exception('coursemodule_visible_for_user() can not be used any more,
+            please use \core_availability\info_module::is_user_visible()');
 }
 
 /**
index 63b58df..1c68f52 100644 (file)
@@ -42,4 +42,5 @@ $string['presentationoraltrequired'] = 'Images must have a description, except i
 $string['preview'] = 'Preview';
 $string['saveimage'] = 'Save image';
 $string['size'] = 'Size';
-$string['width'] = 'Width';
+$string['uploading'] = 'Uploading, please wait...';
+$string['width'] = 'Width';
\ No newline at end of file
index 32b8745..00b3915 100644 (file)
@@ -49,6 +49,7 @@ function atto_image_strings_for_js() {
         'presentationoraltrequired',
         'size',
         'width',
+        'uploading',
     );
 
     $PAGE->requires->strings_for_js($strings, 'atto_image');
index 7aca606..e0a95e9 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2014111000;        // The current plugin version (Date: YYYYMMDDXX).
+$plugin->version   = 2014112800;        // The current plugin version (Date: YYYYMMDDXX).
 $plugin->requires  = 2014110400;        // Requires this Moodle version.
 $plugin->component = 'atto_image';  // Full name of the plugin (used for diagnostics).
index 48a9170..511c6a8 100644 (file)
Binary files a/lib/editor/atto/plugins/image/yui/build/moodle-atto_image-button/moodle-atto_image-button-debug.js and b/lib/editor/atto/plugins/image/yui/build/moodle-atto_image-button/moodle-atto_image-button-debug.js differ
index d346265..8cf00ac 100644 (file)
Binary files a/lib/editor/atto/plugins/image/yui/build/moodle-atto_image-button/moodle-atto_image-button-min.js and b/lib/editor/atto/plugins/image/yui/build/moodle-atto_image-button/moodle-atto_image-button-min.js differ
index 48a9170..511c6a8 100644 (file)
Binary files a/lib/editor/atto/plugins/image/yui/build/moodle-atto_image-button/moodle-atto_image-button.js and b/lib/editor/atto/plugins/image/yui/build/moodle-atto_image-button/moodle-atto_image-button.js differ
index b8f286b..3071112 100644 (file)
@@ -163,6 +163,7 @@ var CSS = {
                 '{{#if presentation}}role="presentation" {{/if}}' +
                 'style="{{alignment}}{{margin}}{{customstyle}}"' +
                 '{{#if classlist}}class="{{classlist}}" {{/if}}' +
+                '{{#if id}}id="{{id}}" {{/if}}' +
                 '/>';
 
 Y.namespace('M.atto_image').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
@@ -204,6 +205,7 @@ Y.namespace('M.atto_image').Button = Y.Base.create('button', Y.M.editor_atto.Edi
     _rawImageDimensions: null,
 
     initializer: function() {
+
         this.addButton({
             icon: 'e/insert_edit_image',
             callback: this._displayDialogue,
@@ -211,6 +213,121 @@ Y.namespace('M.atto_image').Button = Y.Base.create('button', Y.M.editor_atto.Edi
             tagMatchRequiresAll: false
         });
         this.editor.delegate('dblclick', this._handleDoubleClick, 'img', this);
+        this.editor.on('drop', this._handleDragDrop, this);
+    },
+
+    /**
+     * Handle a drag and drop event with an image.
+     *
+     * @method _handleDragDrop
+     * @param {EventFacade} e
+     * @private
+     */
+    _handleDragDrop: function(e) {
+
+        var self = this,
+            host = this.get('host'),
+            template = Y.Handlebars.compile(IMAGETEMPLATE);
+
+        host.saveSelection();
+        e = e._event;
+
+        // Only handle the event if an image file was dropped in.
+        if (e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files.length && /^image\//.test(e.dataTransfer.files[0].type)) {
+
+            var options = host.get('filepickeroptions').image,
+                savepath = (options.savepath === undefined) ? '/' : options.savepath,
+                formData = new FormData(),
+                timestamp = 0,
+                uploadid = "",
+                xhr = new XMLHttpRequest(),
+                imagehtml = "",
+                keys = Object.keys(options.repositories);
+
+            e.preventDefault();
+            e.stopPropagation();
+            formData.append('repo_upload_file', e.dataTransfer.files[0]);
+            formData.append('itemid', options.itemid);
+
+            // List of repositories is an object rather than an array.  This makes iteration more awkward.
+            for (var i = 0; i < keys.length; i++) {
+                if (options.repositories[keys[i]].type === 'upload') {
+                    formData.append('repo_id', options.repositories[keys[i]].id);
+                    break;
+                }
+            }
+            formData.append('env', options.env);
+            formData.append('sesskey', M.cfg.sesskey);
+            formData.append('client_id', options.client_id);
+            formData.append('savepath', savepath);
+            formData.append('ctx_id', options.context.id);
+
+            // Insert spinner as a placeholder.
+            timestamp = new Date().getTime();
+            uploadid = 'moodleimage_' + Math.round(Math.random() * 100000) + '-' + timestamp;
+            host.focus();
+            host.restoreSelection();
+            imagehtml = template({
+                url: M.util.image_url("i/loading_small", 'moodle'),
+                alt: M.util.get_string('uploading', COMPONENTNAME),
+                id: uploadid
+            });
+            host.insertContentAtFocusPoint(imagehtml);
+            self.markUpdated();
+
+            // Kick off a XMLHttpRequest.
+            xhr.onreadystatechange = function() {
+                var placeholder = self.editor.one('#' + uploadid),
+                    result,
+                    file,
+                    newhtml,
+                    newimage;
+
+                if (xhr.readyState === 4) {
+                    if (xhr.status === 200) {
+                        result = JSON.parse(xhr.responseText);
+                        if (result) {
+                            if (result.error) {
+                                if (placeholder) {
+                                    placeholder.remove(true);
+                                }
+                                return new M.core.ajaxException(result);
+                            }
+
+                            file = result;
+                            if (result.event && result.event === 'fileexists') {
+                                // A file with this name is already in use here - rename to avoid conflict.
+                                // Chances are, it's a different image (stored in a different folder on the user's computer).
+                                // If the user wants to reuse an existing image, they can copy/paste it within the editor.
+                                file = result.newfile;
+                            }
+
+                            // Replace placeholder with actual image.
+                            newhtml = template({
+                                url: file.url,
+                                presentation: true
+                            });
+                            newimage = Y.Node.create(newhtml);
+                            if (placeholder) {
+                                placeholder.replace(newimage);
+                            } else {
+                                self.editor.appendChild(newimage);
+                            }
+                            self.markUpdated();
+                        }
+                    } else {
+                        alert(M.util.get_string('servererror', 'moodle'));
+                        if (placeholder) {
+                            placeholder.remove(true);
+                        }
+                    }
+                }
+            };
+            xhr.open("POST", M.cfg.wwwroot + '/repository/repository_ajax.php?action=upload', true);
+            xhr.send(formData);
+        }
+        return false;
+
     },
 
     /**
index 2df6862..75072e2 100644 (file)
@@ -2278,6 +2278,16 @@ abstract class enrol_plugin {
         force_current_language($oldforcelang);
     }
 
+    /**
+     * Backup execution step hook to annotate custom fields.
+     *
+     * @param backup_enrolments_execution_step $step
+     * @param stdClass $enrol
+     */
+    public function backup_annotate_custom_fields(backup_enrolments_execution_step $step, stdClass $enrol) {
+        // Override as necessary to annotate custom fields in the enrol table.
+    }
+
     /**
      * Automatic enrol sync executed during restore.
      * Useful for automatic sync by course->idnumber or course category.
index 7553ff6..b101a40 100644 (file)
@@ -3166,14 +3166,6 @@ class curl {
      * @return resource The curl handle
      */
     private function apply_opt($curl, $options) {
-        // Some more security first.
-        if (defined('CURLOPT_PROTOCOLS')) {
-            $this->options['CURLOPT_PROTOCOLS'] = (CURLPROTO_HTTP | CURLPROTO_HTTPS);
-        }
-        if (defined('CURLOPT_REDIR_PROTOCOLS')) {
-            $this->options['CURLOPT_REDIR_PROTOCOLS'] = (CURLPROTO_HTTP | CURLPROTO_HTTPS);
-        }
-
         // Clean up
         $this->cleanopt();
         // set cookie
@@ -3235,12 +3227,14 @@ class curl {
             $this->options['CURLOPT_FOLLOWLOCATION'] = 0;
         }
 
+        // Limit the protocols to HTTP and HTTPS.
+        if (defined('CURLOPT_PROTOCOLS')) {
+            $this->options['CURLOPT_PROTOCOLS'] = (CURLPROTO_HTTP | CURLPROTO_HTTPS);
+            $this->options['CURLOPT_REDIR_PROTOCOLS'] = (CURLPROTO_HTTP | CURLPROTO_HTTPS);
+        }
+
         // Set options.
         foreach($this->options as $name => $val) {
-            if ($name === 'CURLOPT_PROTOCOLS' or $name === 'CURLOPT_REDIR_PROTOCOLS') {
-                // These can not be changed, sorry.
-                continue;
-            }
             if ($name === 'CURLOPT_FOLLOWLOCATION' and $this->emulateredirects) {
                 // The redirects are emulated elsewhere.
                 curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 0);
index 1291d31..b6f0c17 100644 (file)
@@ -2595,7 +2595,7 @@ class MoodleQuickForm_Renderer extends HTML_QuickForm_Renderer_Tableless{
 
         'fieldset'=>"\n\t\t".'<div id="{id}" class="fitem {advanced}<!-- BEGIN required --> required<!-- END required --> fitem_{type} {emptylabel}"><div class="fitemtitle"><div class="fgrouplabel"><label>{label}<!-- BEGIN required -->{req}<!-- END required -->{advancedimg} </label>{help}</div></div><fieldset class="felement {type}<!-- BEGIN error --> error<!-- END error -->"><!-- BEGIN error --><span class="error">{error}</span><br /><!-- END error -->{element}</fieldset></div>',
 
-        'static'=>"\n\t\t".'<div class="fitem {advanced} {emptylabel}"><div class="fitemtitle"><div class="fstaticlabel"><label>{label}<!-- BEGIN required -->{req}<!-- END required -->{advancedimg} </label>{help}</div></div><div class="felement fstatic <!-- BEGIN error --> error<!-- END error -->"><!-- BEGIN error --><span class="error">{error}</span><br /><!-- END error -->{element}</div></div>',
+        'static'=>"\n\t\t".'<div class="fitem {advanced} {emptylabel}"><div class="fitemtitle"><div class="fstaticlabel">{label}<!-- BEGIN required -->{req}<!-- END required -->{advancedimg} {help}</div></div><div class="felement fstatic <!-- BEGIN error --> error<!-- END error -->"><!-- BEGIN error --><span class="error">{error}</span><br /><!-- END error -->{element}</div></div>',
 
         'warning'=>"\n\t\t".'<div class="fitem {advanced} {emptylabel}">{element}</div>',
 
index 38a4dd8..bfd294a 100644 (file)
@@ -355,7 +355,7 @@ M.util.init_maximised_embed = function(Y, id) {
 
         var headerheight = get_htmlelement_size('page-header', 'height');
         var footerheight = get_htmlelement_size('page-footer', 'height');
-        var newheight = parseInt(Y.one('body').get('winHeight')) - footerheight - headerheight - 100;
+        var newheight = parseInt(Y.one('body').get('docHeight')) - footerheight - headerheight - 100;
         if (newheight < 400) {
             newheight = 400;
         }
index e8a3b9f..2621a3f 100644 (file)
@@ -1065,9 +1065,7 @@ class cm_info implements IteratorAggregate {
         'added' => false,
         'availability' => false,
         'available' => 'get_available',
-        'availablefrom' => 'get_deprecated_available_date',
         'availableinfo' => 'get_available_info',
-        'availableuntil' => 'get_deprecated_available_date',
         'completion' => false,
         'completionexpected' => false,
         'completiongradeitemnumber' => false,
@@ -1095,7 +1093,6 @@ class cm_info implements IteratorAggregate {
         'score' => false,
         'section' => false,
         'sectionnum' => false,
-        'showavailability' => 'get_show_availability',
         'showdescription' => false,
         'uservisible' => 'get_user_visible',
         'visible' => false,
@@ -1185,9 +1182,6 @@ class cm_info implements IteratorAggregate {
 
         // Do not iterate over deprecated properties.
         $props = self::$standardproperties;
-        unset($props['showavailability']);
-        unset($props['availablefrom']);
-        unset($props['availableuntil']);
         unset($props['groupmembersonly']);
 
         foreach ($props as $key => $unused) {
@@ -1798,7 +1792,6 @@ class cm_info implements IteratorAggregate {
         $this->state = self::STATE_BUILDING_DYNAMIC;
 
         if (!empty($CFG->enableavailability)) {
-            require_once($CFG->libdir. '/conditionlib.php');
             // Get availability information.
             $ci = new \core_availability\info_module($this);
 
@@ -1847,35 +1840,6 @@ class cm_info implements IteratorAggregate {
         return $this->available;
     }
 
-    /**
-     * Getter method for property $showavailability. Works by checking the
-     * availableinfo property to see if it's empty or not.
-     *
-     * @return int
-     * @deprecated Since Moodle 2.7
-     */
-    private function get_show_availability() {
-        debugging('$cm->showavailability property has been deprecated. You ' .
-                'can replace it by checking if $cm->availableinfo has content.',
-                DEBUG_DEVELOPER);
-        return ($this->get_available_info() !== '') ? 1 : 0;
-    }
-
-    /**
-     * Getter method for $availablefrom and $availableuntil. Just returns zero
-     * as these are no longer supported.
-     *
-     * @return int Zero
-     * @deprecated Since Moodle 2.7
-     */
-    private function get_deprecated_available_date() {
-        debugging('$cm->availablefrom and $cm->availableuntil have been deprecated. This ' .
-                'information is no longer available as the system provides more complex ' .
-                'options (for example, there might be different dates for different users).',
-                DEBUG_DEVELOPER);
-        return 0;
-    }
-
     /**
      * Getter method for $availablefrom and $availableuntil. Just returns zero
      * as these are no longer supported.
@@ -1906,7 +1870,6 @@ class cm_info implements IteratorAggregate {
      *
      * If the activity is unavailable, additional checks are required to determine if its hidden or greyed out
      *
-     * @see is_user_access_restricted_by_conditional_access()
      * @return void
      */
     private function update_user_visible() {
@@ -1975,33 +1938,13 @@ class cm_info implements IteratorAggregate {
      * Checks whether the module's conditional access settings mean that the
      * user cannot see the activity at all
      *
-     * This is deprecated because it is confusing (name sounds like it's about
-     * access restriction but it is actually about display), is not used
-     * anywhere, and is not necessary. Nobody (outside conditional libraries)
-     * should care what it is that restricted something.
-     *
-     * @return bool True if the user cannot see the module. False if the activity is either available or should be greyed out.
-     * @deprecated since 2.7
+     * @deprecated since 2.7 MDL-44070
      */
     public function is_user_access_restricted_by_conditional_access() {
-        global $CFG;
-        debugging('cm_info::is_user_access_restricted_by_conditional_access() ' .
-                'is deprecated; this function is not needed (use $cm->uservisible ' .
+        throw new coding_exception('cm_info::is_user_access_restricted_by_conditional_access() ' .
+                'can not be used any more; this function is not needed (use $cm->uservisible ' .
                 'and $cm->availableinfo to decide whether it should be available ' .
-                'or appear)', DEBUG_DEVELOPER);
-
-        if (empty($CFG->enableavailability)) {
-            return false;
-        }
-
-        $userid = $this->modinfo->get_user_id();
-        if ($userid == -1) {
-            return null;
-        }
-
-        // Return false if user can access the activity, or if its availability
-        // info is set (= should be displayed even though not accessible).
-        return !$this->get_user_visible() && !$this->get_available_info();
+                'or appear)');
     }
 
     /**
@@ -2698,7 +2641,6 @@ class section_info implements IteratorAggregate {
         $this->_available = true;
         $this->_availableinfo = '';
         if (!empty($CFG->enableavailability)) {
-            require_once($CFG->libdir. '/conditionlib.php');
             // Get availability information.
             $ci = new \core_availability\info_section($this);
             $this->_available = $ci->is_available($this->_availableinfo, true,
@@ -2772,62 +2714,6 @@ class section_info implements IteratorAggregate {
         return $this->_uservisible;
     }
 
-    /**
-     * Getter method for property $showavailability. Works by checking the
-     * availableinfo property to see if it's empty or not.
-     *
-     * @return int
-     * @deprecated Since Moodle 2.7
-     */
-    private function get_showavailability() {
-        debugging('$section->showavailability property has been deprecated. You ' .
-                'can replace it by checking if $section->availableinfo has content.',
-                DEBUG_DEVELOPER);
-        return ($this->get_availableinfo() !== '') ? 1 : 0;
-    }
-
-    /**
-     * Getter method for $availablefrom. Just returns zero as no longer supported.
-     *
-     * @return int Zero
-     * @deprecated Since Moodle 2.7
-     */
-    private function get_availablefrom() {
-        debugging('$section->availablefrom has been deprecated. This ' .
-                'information is no longer available as the system provides more complex ' .
-                'options (for example, there might be different dates for different users).',
-                DEBUG_DEVELOPER);
-        return 0;
-    }
-
-    /**
-     * Getter method for $availablefrom. Just returns zero as no longer supported.
-     *
-     * @return int Zero
-     * @deprecated Since Moodle 2.7
-     */
-    private function get_availableuntil() {
-        debugging('$section->availableuntil has been deprecated. This ' .
-                'information is no longer available as the system provides more complex ' .
-                'options (for example, there might be different dates for different users).',
-                DEBUG_DEVELOPER);
-        return 0;
-    }
-
-    /**
-     * Getter method for $groupingid. Just returns zero as no longer supported.
-     *
-     * @return int Zero
-     * @deprecated Since Moodle 2.7
-     */
-    private function get_groupingid() {
-        debugging('$section->groupingid has been deprecated. This ' .
-                'information is no longer available as the system provides more complex ' .
-                'options (for example, combining multiple groupings).',
-                DEBUG_DEVELOPER);
-        return 0;
-    }
-
     /**
      * Restores the course_sections.sequence value
      *
index 6dced9e..25e51ea 100644 (file)
@@ -8664,7 +8664,8 @@ function getremoteaddr($default='0.0.0.0') {
             } else {
                 // Remove port from IPv4.
                 if (substr_count($address, ":") == 1) {
-                    $address = explode(":", $address)[0];
+                    $parts = explode(":", $address);
+                    $address = $parts[0];
                 }
             }
 
index 3a53d86..6db3cd2 100644 (file)
@@ -1297,17 +1297,8 @@ class global_navigation extends navigation_node {
         }
 
         // Give the local plugins a chance to include some navigation if they want.
-        foreach (core_component::get_plugin_list_with_file('local', 'lib.php', true) as $plugin => $file) {
-            $function = "local_{$plugin}_extends_navigation";
-            $oldfunction = "{$plugin}_extends_navigation";
-            if (function_exists($function)) {
-                // This is the preferred function name as there is less chance of conflicts
-                $function($this);
-            } else if (function_exists($oldfunction)) {
-                // We continue to support the old function name to ensure backwards compatibility
-                debugging("Deprecated local plugin navigation callback: Please rename '{$oldfunction}' to '{$function}'. Support for the old callback will be dropped after the release of 2.4", DEBUG_DEVELOPER);
-                $oldfunction($this);
-            }
+        foreach (get_plugin_list_with_function('local', 'extends_navigation') as $function) {
+            $function($this);
         }
 
         // Remove any empty root nodes
@@ -4150,8 +4141,8 @@ class settings_navigation extends navigation_node {
                     return false;
                 }
                 $canaccessallgroups = has_capability('moodle/site:accessallgroups', $coursecontext);
-                if (!$canaccessallgroups && groups_get_course_groupmode($course) == SEPARATEGROUPS) {
-                    // If groups are in use, make sure we can see that group (MDL-45874).
+                if (!$canaccessallgroups && groups_get_course_groupmode($course) == SEPARATEGROUPS && !$canviewuser) {
+                    // If groups are in use, make sure we can see that group (MDL-45874). That does not apply to parents.
                     if ($courseid == $this->page->course->id) {
                         $mygroups = get_fast_modinfo($this->page->course)->groups;
                     } else {
index 3a2e70a..4752d7f 100644 (file)
@@ -2861,7 +2861,13 @@ class custom_menu extends custom_menu_item {
                             $itemtitle = $itemtext;
                             break;
                         case 1:
-                            $itemurl = new moodle_url($setting);
+                            try {
+                                $itemurl = new moodle_url($setting);
+                            } catch (moodle_exception $exception) {
+                                // We're not actually worried about this, we don't want to mess up the display
+                                // just for a wrongly entered URL.
+                                $itemurl = null;
+                            }
                             break;
                         case 2:
                             $itemtitle = $setting;
index 4c98d90..e35d6a9 100644 (file)
@@ -2382,7 +2382,6 @@ class core_renderer extends renderer_base {
 
         $attributes = array('href'=>$url);
         if (!$userpicture->visibletoscreenreaders) {
-            $attributes['role'] = 'presentation';
             $attributes['tabindex'] = '-1';
             $attributes['aria-hidden'] = 'true';
         }
diff --git a/lib/sessionkeepalive_ajax.php b/lib/sessionkeepalive_ajax.php
new file mode 100644 (file)
index 0000000..7a930ff
--- /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/>.
+
+/**
+ * Ensure that session is kept alive.
+ *
+ * @copyright 2014 Andrew Nicols
+ * @package   core
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+define('AJAX_SCRIPT', true);
+require_once(dirname(__DIR__) . '/config.php');
+
+// Require the session key - want to make sure that this isn't called
+// maliciously to keep a session alive longer than intended.
+if (!confirm_sesskey()) {
+    header('HTTP/1.1 403 Forbidden');
+    print_error('invalidsesskey');
+}
+
+// Update the session.
+\core\session\manager::touch_session(session_id());
index 035e0f8..d616c77 100644 (file)
@@ -2579,7 +2579,6 @@ class core_accesslib_testcase extends advanced_testcase {
 
         foreach ($DB->get_records('context') as $contextid => $record) {
             $context = context::instance_by_id($contextid);
-            $this->assertEquals($context, get_context_instance_by_id($contextid, IGNORE_MISSING));
             $this->assertEquals($context, get_context_instance($record->contextlevel, $record->instanceid));
             $this->assertEquals($context->get_parent_context_ids(), get_parent_contexts($context));
             if ($context->id == SYSCONTEXTID) {
@@ -2599,8 +2598,6 @@ class core_accesslib_testcase extends advanced_testcase {
         // Make sure a debugging is thrown.
         get_context_instance($record->contextlevel, $record->instanceid);
         $this->assertDebuggingCalled('get_context_instance() is deprecated, please use context_xxxx::instance() instead.', DEBUG_DEVELOPER);
-        get_context_instance_by_id($record->id);
-        $this->assertDebuggingCalled('get_context_instance_by_id() is deprecated, please use context::instance_by_id($id) instead.', DEBUG_DEVELOPER);
         get_system_context();
         $this->assertDebuggingCalled('get_system_context() is deprecated, please use context_system::instance() instead.', DEBUG_DEVELOPER);
         get_parent_contexts($context);
index 3a957dc..a405408 100644 (file)
@@ -91,7 +91,6 @@ class behat_data_generators extends behat_base {
             'datagenerator' => 'enrol_user',
             'required' => array('user', 'course', 'role'),
             'switchids' => array('user' => 'userid', 'course' => 'courseid', 'role' => 'roleid')
-
         ),
         'permission overrides' => array(
             'datagenerator' => 'permission_override',
@@ -156,7 +155,17 @@ class behat_data_generators extends behat_base {
             'datagenerator' => 'scale',
             'required' => array('name', 'scale'),
             'switchids' => array('course' => 'courseid')
-        )
+        ),
+        'question categories' => array(
+            'datagenerator' => 'question_category',
+            'required' => array('name', 'contextlevel', 'reference'),
+            'switchids' => array('questioncategory' => 'category')
+        ),
+        'questions' => array(
+            'datagenerator' => 'question',
+            'required' => array('qtype', 'questioncategory', 'name'),
+            'switchids' => array('questioncategory' => 'category', 'user' => 'createdby')
+        ),
     );
 
     /**
@@ -473,6 +482,49 @@ class behat_data_generators extends behat_base {
         cohort_add_member($data['cohortid'], $data['userid']);
     }
 
+    /**
+     * Create a question category.
+     *
+     * @param array $data the row of data from the behat script.
+     */
+    protected function process_question_category($data) {
+        $context = $this->get_context($data['contextlevel'], $data['reference']);
+        $data['contextid'] = $context->id;
+        $this->datagenerator->get_plugin_generator('core_question')->create_question_category($data);
+    }
+
+    /**
+     * Create a question.
+     *
+     * Creating questions relies on the question/type/.../tests/helper.php mechanism.
+     * We start with test_question_maker::get_question_form_data($data['qtype'], $data['template'])
+     * and then overlay the values from any other fields of $data that are set.
+     *
+     * @param array $data the row of data from the behat script.
+     */
+    protected function process_question($data) {
+        if (array_key_exists('questiontext', $data)) {
+            $data['questiontext'] = array(
+                    'text'   => $data['questiontext'],
+                    'format' => FORMAT_HTML,
+                );
+        }
+
+        if (array_key_exists('generalfeedback', $data)) {
+            $data['generalfeedback'] = array(
+                    'text'   => $data['generalfeedback'],
+                    'format' => FORMAT_HTML,
+                );
+        }
+
+        $which = null;
+        if (!empty($data['template'])) {
+            $which = $data['template'];
+        }
+
+        $this->datagenerator->get_plugin_generator('core_question')->create_question($data['qtype'], $which, $data);
+    }
+
     /**
      * Gets the grade category id from the grade category fullname
      * @throws Exception
@@ -616,10 +668,9 @@ class behat_data_generators extends behat_base {
     }
 
     /**
-     * Gets the course id from its name.
-     * @throws Exception
-     * @param string $name
-     * @return int
+     * Get the id of a named scale.
+     * @param string $name the name of the scale.
+     * @return int the scale id.
      */
     protected function get_scale_id($name) {
         global $DB;
@@ -630,6 +681,27 @@ class behat_data_generators extends behat_base {
         return $id;
     }
 
+    /**
+     * Get the id of a named question category (must be globally unique).
+     * Note that 'Top' is a special value, used when setting the parent of another
+     * category, meaning top-level.
+     *
+     * @param string $name the question category name.
+     * @return int the question category id.
+     */
+    protected function get_questioncategory_id($name) {
+        global $DB;
+
+        if ($name == 'Top') {
+            return 0;
+        }
+
+        if (!$id = $DB->get_field('question_categories', 'id', array('name' => $name))) {
+            throw new Exception('The specified question category with name "' . $name . '" does not exist');
+        }
+        return $id;
+    }
+
     /**
      * Gets the internal context id from the context reference.
      *
index bed16a9..495a486 100644 (file)
@@ -43,491 +43,248 @@ use Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException,
 class behat_deprecated extends behat_base {
 
     /**
-     * Click on the specified element inside a table row containing the specified text.
-     *
-     * @deprecated since Moodle 2.7 MDL-42627
-     * @todo MDL-42862 This will be deleted in Moodle 2.9
-     * @see behat_general::i_click_on_in_the()
-     *
      * @Given /^I click on "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>(?:[^"]|\\")*)" in the "(?P<row_text_string>(?:[^"]|\\")*)" table row$/
-     * @throws ElementNotFoundException
-     * @param string $element Element we look for
-     * @param string $selectortype The type of what we look for
-     * @param string $tablerowtext The table row text
+     * @deprecated since Moodle 2.7 MDL-42627 - please do not use this step any more.
      */
     public function i_click_on_in_the_table_row($element, $selectortype, $tablerowtext) {
-
-        // Throw an exception if deprecated methods are not allowed otherwise allow it's execution.
         $alternative = 'I click on "' . $this->escape($element) . '" "' . $this->escape($selectortype) .
             '" in the "' . $this->escape($tablerowtext) . '" "table_row"';
-        $this->deprecated_message($alternative);
-
-        return new Given($alternative);
+        $this->deprecated_message($alternative, true);
     }
 
     /**
-     * Goes to notification page ensuring site admin navigation is loaded.
-     *
-     * Step [I expand "Site administration" node] will ensure that administration menu
-     * is opened in both javascript and non-javascript modes.
-     *
-     * @deprecated since 2.7
-     * @todo MDL-42862 This will be deleted in Moodle 2.9
-     *
      * @Given /^I go to notifications page$/
-     * @return Given[]
+     * @deprecated since Moodle 2.7 MDL-42731 - please do not use this step any more.
      */
     public function i_go_to_notifications_page() {
         $alternative = array(
             'I expand "' . get_string('administrationsite') . '" node',
             'I click on "' . get_string('notifications') . '" "link" in the "'.get_string('administration').'" "block"'
         );
-        $this->deprecated_message($alternative);
-        return array(
-            new Given($alternative[0]),
-            new Given($alternative[1]),
-        );
+        $this->deprecated_message($alternative, true);
     }
 
     /**
-     * Adds the specified file from the 'Recent files' repository to the specified filepicker of the current page.
-     *
-     * @deprecated since 2.7
-     * @todo MDL-42862 This will be deleted in Moodle 2.9
-     * @see behat_filepicker::i_add_file_from_repository_to_filemanager()
-     *
      * @When /^I add "(?P<filename_string>(?:[^"]|\\")*)" file from recent files to "(?P<filepicker_field_string>(?:[^"]|\\")*)" filepicker$/
-     * @param string $filename
-     * @param string $filepickerelement
+     * @deprecated since Moodle 2.7 MDL-42174 - please do not use this step any more.
      */
     public function i_add_file_from_recent_files_to_filepicker($filename, $filepickerelement) {
         $reponame = get_string('pluginname', 'repository_recent');
         $alternative = 'I add "' . $this->escape($filename) . '" file from "' .
                 $reponame . '" to "' . $this->escape($filepickerelement) . '" filemanager';
-        $this->deprecated_message($alternative);
-        return array(
-            new Given($alternative)
-        );
+        $this->deprecated_message($alternative, true);
     }
 
     /**
-     * Uploads a file to the specified filemanager leaving other fields in upload form default. The paths should be relative to moodle codebase.
-     *
-     * @deprecated since 2.7
-     * @todo MDL-42862 This will be deleted in Moodle 2.9
-     * @see behat_repository_upload::i_upload_file_to_filemanager()
-     *
      * @When /^I upload "(?P<filepath_string>(?:[^"]|\\")*)" file to "(?P<filepicker_field_string>(?:[^"]|\\")*)" filepicker$/
-     * @throws ExpectationException Thrown by behat_base::find
-     * @param string $filepath
-     * @param string $filepickerelement
+     * @deprecated since Moodle 2.7 MDL-42174 - please do not use this step any more.
      */
     public function i_upload_file_to_filepicker($filepath, $filepickerelement) {
         $alternative = 'I upload "' . $this->escape($filepath) . '" file to "' .
                 $this->escape($filepickerelement) . '" filemanager';
-        $this->deprecated_message($alternative);
-        return array(
-            new Given($alternative)
-        );
+        $this->deprecated_message($alternative, true);
     }
 
     /**
-     * Creates a folder with specified name in the current folder and in the specified filepicker field.
-     *
-     * @deprecated since 2.7
-     * @todo MDL-42862 This will be deleted in Moodle 2.9
-     * @see behat_filepicker::i_create_folder_in_filemanager()
-     *
      * @Given /^I create "(?P<foldername_string>(?:[^"]|\\")*)" folder in "(?P<filepicker_field_string>(?:[^"]|\\")*)" filepicker$/
-     * @throws ExpectationException Thrown by behat_base::find
-     * @param string $foldername
-     * @param string $filepickerelement
+     * @deprecated since Moodle 2.7 MDL-42174 - please do not use this step any more.
      */
     public function i_create_folder_in_filepicker($foldername, $filepickerelement) {
         $alternative = 'I create "' . $this->escape($foldername) .
                 '" folder in "' . $this->escape($filepickerelement) . '" filemanager';
-        $this->deprecated_message($alternative);
-        return array(new Given($alternative));
+        $this->deprecated_message($alternative, true);
     }
 
     /**
-     * Opens the contents of a filepicker folder. It looks for the folder in the current folder and in the path bar.
-     *
-     * @deprecated since 2.7
-     * @todo MDL-42862 This will be deleted in Moodle 2.9
-     * @see behat_filepicker::i_open_folder_from_filemanager()
-     *
      * @Given /^I open "(?P<foldername_string>(?:[^"]|\\")*)" folder from "(?P<filepicker_field_string>(?:[^"]|\\")*)" filepicker$/
-     * @throws ExpectationException Thrown by behat_base::find
-     * @param string $foldername
-     * @param string $filepickerelement
+     * @deprecated since Moodle 2.7 MDL-42174 - please do not use this step any more.
      */
     public function i_open_folder_from_filepicker($foldername, $filepickerelement) {
         $alternative = 'I open "' . $this->escape($foldername) . '" folder from "' .
                 $this->escape($filepickerelement) . '" filemanager';
-        $this->deprecated_message($alternative);
-        return array(new Given($alternative));
+        $this->deprecated_message($alternative, true);
     }
 
     /**
-     * Unzips the specified file from the specified filepicker field. The zip file has to be visible in the current folder.
-     *
-     * @deprecated since 2.7
-     * @todo MDL-42862 This will be deleted in Moodle 2.9
-     * @see behat_filepicker::i_unzip_file_from_filemanager()
-     *
      * @Given /^I unzip "(?P<filename_string>(?:[^"]|\\")*)" file from "(?P<filepicker_field_string>(?:[^"]|\\")*)" filepicker$/
-     * @throws ExpectationException Thrown by behat_base::find
-     * @param string $filename
-     * @param string $filepickerelement
+     * @deprecated since Moodle 2.7 MDL-42174 - please do not use this step any more.
      */
     public function i_unzip_file_from_filepicker($filename, $filepickerelement) {
         $alternative = 'I unzip "' . $this->escape($filename) . '" file from "' .
                 $this->escape($filepickerelement) . '" filemanager';
-        $this->deprecated_message($alternative);
-        return array(new Given($alternative));
+        $this->deprecated_message($alternative, true);
     }
 
     /**
-     * Zips the specified folder from the specified filepicker field. The folder has to be in the current folder.
-     *
-     * @deprecated since 2.7
-     * @todo MDL-42862 This will be deleted in Moodle 2.9
-     * @see behat_filepicker::i_zip_folder_from_filemanager()
-     *
      * @Given /^I zip "(?P<filename_string>(?:[^"]|\\")*)" folder from "(?P<filepicker_field_string>(?:[^"]|\\")*)" filepicker$/
-     * @throws ExpectationException Thrown by behat_base::find
-     * @param string $foldername
-     * @param string $filepickerelement
+     * @deprecated since Moodle 2.7 MDL-42174 - please do not use this step any more.
      */
     public function i_zip_folder_from_filepicker($foldername, $filepickerelement) {
         $alternative = 'I zip "' . $this->escape($foldername) . '" folder from "' .
                 $this->escape($filepickerelement) . '" filemanager';
-        $this->deprecated_message($alternative);
-        return array(new Given($alternative));
+        $this->deprecated_message($alternative, true);
     }
 
     /**
-     * Deletes the specified file or folder from the specified filepicker field.
-     *
-     * @deprecated since 2.7
-     * @todo MDL-42862 This will be deleted in Moodle 2.9
-     * @see behat_filepicker::i_delete_file_from_filemanager()
-     *
      * @Given /^I delete "(?P<file_or_folder_name_string>(?:[^"]|\\")*)" from "(?P<filepicker_field_string>(?:[^"]|\\")*)" filepicker$/
-     * @throws ExpectationException Thrown by behat_base::find
-     * @param string $name
-     * @param string $filepickerelement
+     * @deprecated since Moodle 2.7 MDL-42174 - please do not use this step any more.
      */
     public function i_delete_file_from_filepicker($name, $filepickerelement) {
         $alternative = 'I delete "' . $this->escape($name) . '" from "' .
                 $this->escape($filepickerelement) . '" filemanager';
-        $this->deprecated_message($alternative);
-        return array(new Given($alternative));
+        $this->deprecated_message($alternative, true);
     }
 
     /**
-     * Sends a message to the specified user from the logged user.
-     *
-     * @deprecated since 2.7
-     * @todo MDL-42862 This will be deleted in Moodle 2.9
-     * @see behat_message::i_send_message_to_user()
-     *
      * @Given /^I send "(?P<message_contents_string>(?:[^"]|\\")*)" message to "(?P<username_string>(?:[^"]|\\")*)"$/
-     * @throws ElementNotFoundException
-     * @param string $messagecontent
-     * @param string $tousername
+     * @deprecated since Moodle 2.7 MDL-43584 - please do not use this step any more.
      */
     public function i_send_message_to_user($messagecontent, $tousername) {
-
-        global $DB;
-
-        // Runs by CLI, same PHP process that created the user.
-        $touser = $DB->get_record('user', array('username' => $tousername));
-        if (!$touser) {
-            throw new ElementNotFoundException($this->getSession(), '"' . $tousername . '" ');
-        }
-        $tofullname = fullname($touser);
-
-        $alternative = 'I send "' . $this->escape($messagecontent) . '" message to "' . $tofullname . '" user';
-        $this->deprecated_message($alternative);
-        return new Given($alternative);
+        $alternative = 'I send "' . $this->escape($messagecontent) . '" message to "USER_FULL_NAME" user';
+        $this->deprecated_message($alternative, true);
     }
 
     /**
-     * Adds the user to the specified cohort.
-     *
-     * @deprecated since 2.7
-     * @todo MDL-42862 This will be deleted in Moodle 2.9
-     * @see behat_cohort::i_add_user_to_cohort_members()
-     *
      * @Given /^I add "(?P<user_username_string>(?:[^"]|\\")*)" user to "(?P<cohort_idnumber_string>(?:[^"]|\\")*)" cohort$/
-     * @param string $username
-     * @param string $cohortidnumber
+     * @deprecated since Moodle 2.7 MDL-43584 - please do not use this step any more.
      */
     public function i_add_user_to_cohort($username, $cohortidnumber) {
-        global $DB;
-
-        // The user was created by the data generator, executed by the same PHP process that is
-        // running this step, not by any Selenium action.
-        $user = $DB->get_record('user', array('username' => $username));
-        $userlocator = $user->firstname . ' ' . $user->lastname . ' (' . $user->email . ')';
-
-        $alternative = 'I add "' . $this->escape($userlocator) .
-            '" user to "' . $this->escape($cohortidnumber) . '" cohort members';
-        $this->deprecated_message($alternative);
-
-        return new Given($alternative);
+        $alternative = 'I add "USER_FIRST_NAME USER_LAST_NAME (USER_EMAIL)" user to "'
+                . $this->escape($cohortidnumber) . '" cohort members';
+        $this->deprecated_message($alternative, true);
     }
 
     /**
-     * Add the specified user to the group. You should be in the groups page when running this step.
-     *
-     * @deprecated since 2.7
-     * @todo MDL-42862 This will be deleted in Moodle 2.9
-     * @see behat_groups::i_add_user_to_group_members()
-     *
      * @Given /^I add "(?P<username_string>(?:[^"]|\\")*)" user to "(?P<group_name_string>(?:[^"]|\\")*)" group$/
-     * @param string $username
-     * @param string $groupname
+     * @deprecated since Moodle 2.7 MDL-43584 - please do not use this step any more.
      */
     public function i_add_user_to_group($username, $groupname) {
-        global $DB;
-
-        $user = $DB->get_record('user', array('username' => $username));
-        $userfullname = fullname($user);
-
-        $alternative = 'I add "' . $this->escape($userfullname) .
-            '" user to "' . $this->escape($groupname) . '" group members';
-        $this->deprecated_message($alternative);
-
-        return new Given($alternative);
+        $alternative = 'I add "USER_FULL_NAME" user to "' . $this->escape($groupname) . '" group members';
+        $this->deprecated_message($alternative, true);
     }
 
     /**
-     * Fills in form text field with specified id|name|label|value. It works with text-based fields.
-     *
-     * @deprecated since 2.7
-     * @todo MDL-42862 This will be deleted in Moodle 2.9
-     * @see behat_forms::i_set_the_field_to()
-     *
      * @When /^I fill in "(?P<field_string>(?:[^"]|\\")*)" with "(?P<value_string>(?:[^"]|\\")*)"$/
-     * @throws ElementNotFoundException Thrown by behat_base::find
-     * @param string $field
-     * @param string $value
+     * @deprecated since Moodle 2.7 MDL-43738 - please do not use this step any more.
      */
     public function fill_field($field, $value) {
         $alternative = 'I set the field "' . $this->escape($field) . '" to "' . $this->escape($value) . '"';
-        $this->deprecated_message($alternative);
-
-        return new Given($alternative);
+        $this->deprecated_message($alternative, true);
     }
 
     /**
-     * Selects option in select field with specified id|name|label|value.
-     *
-     * @deprecated since 2.7
-     * @todo MDL-42862 This will be deleted in Moodle 2.9
-     * @see behat_forms::i_set_the_field_to()
-     *
      * @When /^I select "(?P<option_string>(?:[^"]|\\")*)" from "(?P<select_string>(?:[^"]|\\")*)"$/
-     * @throws ElementNotFoundException Thrown by behat_base::find
-     * @param string $option
-     * @param string $select
+     * @deprecated since Moodle 2.7 MDL-43738 - please do not use this step any more.
      */
     public function select_option($option, $select) {
         $alternative = 'I set the field "' . $this->escape($select) . '" to "' . $this->escape($option) . '"';
-        $this->deprecated_message($alternative);
-
-        return new Given($alternative);
+        $this->deprecated_message($alternative, true);
     }
 
     /**
-     * Selects the specified id|name|label from the specified radio button.
-     *
-     * @deprecated since 2.7
-     * @todo MDL-42862 This will be deleted in Moodle 2.9
-     * @see behat_forms::i_set_the_field_to()
-     *
      * @When /^I select "(?P<radio_button_string>(?:[^"]|\\")*)" radio button$/
-     * @throws ElementNotFoundException Thrown by behat_base::find
-     * @param string $radio The radio button id, name or label value
+     * @deprecated since Moodle 2.7 MDL-43738 - please do not use this step any more.
      */
     public function select_radio($radio) {
         $alternative = 'I set the field "' . $this->escape($radio) . '" to "1"';
-        $this->deprecated_message($alternative);
-
-        return new Given($alternative);
+        $this->deprecated_message($alternative, true);
     }
 
     /**
-     * Checks checkbox with specified id|name|label|value.
-     *
-     * @deprecated since 2.7
-     * @todo MDL-42862 This will be deleted in Moodle 2.9
-     * @see behat_forms::i_set_the_field_to()
-     *
      * @When /^I check "(?P<option_string>(?:[^"]|\\")*)"$/
-     * @throws ElementNotFoundException Thrown by behat_base::find
-     * @param string $option
+     * @deprecated since Moodle 2.7 MDL-43738 - please do not use this step any more.
      */
     public function check_option($option) {
         $alternative = 'I set the field "' . $this->escape($option) . '" to "1"';
-        $this->deprecated_message($alternative);
-
-        return new Given($alternative);
+        $this->deprecated_message($alternative, true);
     }
 
     /**
-     * Unchecks checkbox with specified id|name|label|value.
-     *
-     * @deprecated since 2.7
-     * @todo MDL-42862 This will be deleted in Moodle 2.9
-     * @see behat_forms::i_set_the_field_to()
-     *
      * @When /^I uncheck "(?P<option_string>(?:[^"]|\\")*)"$/
-     * @throws ElementNotFoundException Thrown by behat_base::find
-     * @param string $option
+     * @deprecated since Moodle 2.7 MDL-43738 - please do not use this step any more.
      */
     public function uncheck_option($option) {
         $alternative = 'I set the field "' . $this->escape($option) . '" to ""';
-        $this->deprecated_message($alternative);
-
-        return new Given($alternative);
+        $this->deprecated_message($alternative, true);
     }
 
     /**
-     * Checks that the field matches the specified value. When using multi-select fields use commas to separate selected options.
-     *
-     * @deprecated since 2.7
-     * @todo MDL-42862 This will be deleted in Moodle 2.9
-     * @see behat_forms::the_field_matches_value()
-     *
      * @Then /^the "(?P<field_string>(?:[^"]|\\")*)" field should match "(?P<value_string>(?:[^"]|\\")*)" value$/
-     * @throws ExpectationException
-     * @throws ElementNotFoundException Thrown by behat_base::find
-     * @param string $locator
-     * @param string $value
+     * @deprecated since Moodle 2.7 MDL-43738 - please do not use this step any more.
      */
     public function the_field_should_match_value($locator, $value) {
         $alternative = 'the field "' . $this->escape($locator) . '" matches value "' . $this->escape($value) . '"';
-        $this->deprecated_message($alternative);
-
-        return new Then($alternative);
+        $this->deprecated_message($alternative, true);
     }
 
     /**
-     * Checks, that checkbox with specified in|name|label|value is checked.
-     *
-     * @deprecated since 2.7
-     * @todo MDL-42862 This will be deleted in Moodle 2.9
-     * @see behat_forms::the_field_matches_value()
-     *
      * @Then /^the "(?P<checkbox_string>(?:[^"]|\\")*)" checkbox should be checked$/
-     * @param string $checkbox
+     * @deprecated since Moodle 2.7 MDL-43738 - please do not use this step any more.
      */
     public function assert_checkbox_checked($checkbox) {
         $alternative = 'the field "' . $this->escape($checkbox) . '" matches value "1"';
-        $this->deprecated_message($alternative);
-
-        return new Then($alternative);
+        $this->deprecated_message($alternative, true);
     }
 
     /**
-     * Checks, that checkbox with specified in|name|label|value is unchecked.
-     *
-     * @deprecated since 2.7
-     * @todo MDL-42862 This will be deleted in Moodle 2.9
-     * @see behat_forms::the_field_matches_value()
-     *
      * @Then /^the "(?P<checkbox_string>(?:[^"]|\\")*)" checkbox should not be checked$/
-     * @param string $checkbox
+     * @deprecated since Moodle 2.7 MDL-43738 - please do not use this step any more.
      */
     public function assert_checkbox_not_checked($checkbox) {
         $alternative = 'the field "' . $this->escape($checkbox) . '" matches value ""';
-        $this->deprecated_message($alternative);
-
-        return new Then($alternative);
+        $this->deprecated_message($alternative, true);
     }
 
     /**
-     * Fills a moodle form with field/value data.
-     *
-     * @deprecated since 2.7
-     * @todo MDL-42862 This will be deleted in Moodle 2.9
-     * @see behat_forms::i_set_the_following_fields_to_these_values()
-     *
      * @Given /^I fill the moodle form with:$/
-     * @throws ElementNotFoundException Thrown by behat_base::find
-     * @param TableNode $data
+     * @deprecated since Moodle 2.7 MDL-43738 - please do not use this step any more.
      */
     public function i_fill_the_moodle_form_with(TableNode $data) {
         $alternative = 'I set the following fields to these values:';
-        $this->deprecated_message($alternative);
-
-        return new Given($alternative, $data);
+        $this->deprecated_message($alternative, true);
     }
 
     /**
-     * Checks the provided element and selector type exists in the current page.
-     *
-     * This step is for advanced users, use it if you don't find anything else suitable for what you need.
-     *
      * @Then /^"(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" should exists$/
-     * @throws ElementNotFoundException Thrown by behat_base::find
-     * @param string $element The locator of the specified selector
-     * @param string $selectortype The selector type
+     * @deprecated since Moodle 2.7 MDL-43236 - please do not use this step any more.
      */
     public function should_exists($element, $selectortype) {
         $alternative = '"' . $this->escape($element) . '" "' . $this->escape($selectortype) . '" should exist';
-        $this->deprecated_message($alternative);
-        return new Then($alternative);
+        $this->deprecated_message($alternative, true);
     }
 
     /**
-     * Checks that the provided element and selector type not exists in the current page.
-     *
-     * This step is for advanced users, use it if you don't find anything else suitable for what you need.
-     *
      * @Then /^"(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" should not exists$/
-     * @throws ExpectationException
-     * @param string $element The locator of the specified selector
-     * @param string $selectortype The selector type
+     * @deprecated since Moodle 2.7 MDL-43236 - please do not use this step any more.
      */
     public function should_not_exists($element, $selectortype) {
         $alternative = '"' . $this->escape($element) . '" "' . $this->escape($selectortype) . '" should not exist';
-        $this->deprecated_message($alternative);
-        return new Then($alternative);
+        $this->deprecated_message($alternative, true);
     }
 
     /**
-     * Creates the specified element. More info about available elements in http://docs.moodle.org/dev/Acceptance_testing#Fixtures.
-     *
      * @Given /^the following "(?P<element_string>(?:[^"]|\\")*)" exists:$/
-     *
-     * @throws Exception
-     * @throws PendingException
-     * @param string    $elementname The name of the entity to add
-     * @param TableNode $data
+     * @deprecated since Moodle 2.7 MDL-43236 - please do not use this step any more.
      */
     public function the_following_exists($elementname, TableNode $data) {
         $alternative = 'the following "' . $this->escape($elementname) . '" exist:';
-        $this->deprecated_message($alternative);
-        return new Given($alternative, $data);
+        $this->deprecated_message($alternative, true);
     }
 
+
     /**
      * Throws an exception if $CFG->behat_usedeprecated is not allowed.
      *
      * @throws Exception
      * @param string|array $alternatives Alternative/s to the requested step
+     * @param bool $throwexception If set to true we always throw exception, irrespective of behat_usedeprecated setting.
      * @return void
      */
-    protected function deprecated_message($alternatives) {
+    protected function deprecated_message($alternatives, $throwexception = false) {
         global $CFG;
 
         // We do nothing if it is enabled.
-        if (!empty($CFG->behat_usedeprecated)) {
+        if (!empty($CFG->behat_usedeprecated) && !$throwexception) {
             return;
         }
 
@@ -535,11 +292,23 @@ class behat_deprecated extends behat_base {
             $alternatives = array($alternatives);
         }
 
-        $message = 'Deprecated step, rather than using this step you can:';
+        // Show an appropriate message based on the throwexception flag.
+        if ($throwexception) {
+            $message = 'This step has been removed. Rather than using this step you can:';
+        } else {
+            $message = 'Deprecated step, rather than using this step you can:';
+        }
+
+        // Add all alternatives to the message.
         foreach ($alternatives as $alternative) {
             $message .= PHP_EOL . '- ' . $alternative;
         }
-        $message .= PHP_EOL . '- Set $CFG->behat_usedeprecated in config.php to allow the use of deprecated steps if you don\'t have any other option';
+
+        if (!$throwexception) {
+            $message .= PHP_EOL . '- Set $CFG->behat_usedeprecated in config.php to allow the use of deprecated steps
+                    if you don\'t have any other option';
+        }
+
         throw new Exception($message);
     }
 
index 1844f10..b0a02c5 100644 (file)
@@ -1039,9 +1039,9 @@ class behat_general extends behat_base {
         } else {
             // Header can be in thead or tbody (first row), following xpath should work.
             $theadheaderxpath = "thead/tr[1]/th[(normalize-space(.)=" . $columnliteral . " or a[normalize-space(text())=" .
-                $columnliteral . "])]";
+                    $columnliteral . "] or div[normalize-space(text())=" . $columnliteral . "])]";
             $tbodyheaderxpath = "tbody/tr[1]/td[(normalize-space(.)=" . $columnliteral . " or a[normalize-space(text())=" .
-                $columnliteral . "])]";
+                    $columnliteral . "] or div[normalize-space(text())=" . $columnliteral . "])]";
 
             // Check if column exists.
             $columnheaderxpath = $tablexpath . "[" . $theadheaderxpath . " | " . $tbodyheaderxpath . "]";
index e7f64f0..6e2653f 100644 (file)
@@ -193,6 +193,9 @@ class behat_hooks extends behat_base {
             behat_context_helper::set_session($session);
         }
 
+        // Reset mink session between the scenarios.
+        $session->reset();
+
         // Reset $SESSION.
         \core\session\manager::init_empty_session();
 
diff --git a/lib/tests/conditionlib_test.php b/lib/tests/conditionlib_test.php
deleted file mode 100644 (file)
index f2b036b..0000000
+++ /dev/null
@@ -1,214 +0,0 @@
-<?php
-// This file is part of Moodle - http://moodle.org/
-//
-// Moodle is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Moodle is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
-
-/**
- * Tests for deprecated conditional activities classes.
- *
- * @package core_availability
- * @copyright 2014 The Open University
- * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
- */
-
-defined('MOODLE_INTERNAL') || die();
-
-global $CFG;
-require_once($CFG->dirroot . '/lib/conditionlib.php');
-
-
-/**
- * Tests for deprecated conditional activities classes.
- *
- * @package core_availability
- * @copyright 2014 The Open University
- * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
- */
-class core_conditionlib_testcase extends advanced_testcase {
-
-    protected function setUp() {
-        global $CFG;
-        parent::setUp();
-
-        $this->resetAfterTest();
-
-        $CFG->enableavailability = 1;
-        $CFG->enablecompletion = 1;
-        $user = $this->getDataGenerator()->create_user();
-        $this->setUser($user);
-    }
-
-    public function test_constructor() {
-        $generator = $this->getDataGenerator();
-        $course = $generator->create_course();
-        $page = $generator->get_plugin_generator('mod_page')->create_instance(
-                array('course' => $course));
-        $modinfo = get_fast_modinfo($course);
-
-        // No ID.
-        try {
-            $test = new condition_info((object)array());
-            $this->fail();
-        } catch (coding_exception $e) {
-            // Do nothing.
-            $this->assertDebuggingCalled();
-        }
-
-        // Get actual cm_info for comparison.
-        $realcm = $modinfo->get_cm($page->cmid);
-
-        // No other data.
-        $test = new condition_info((object)array('id' => $page->cmid));
-        $this->assertDebuggingCalled();
-        $this->assertEquals($realcm, $test->get_full_course_module());
-        $this->assertDebuggingCalled();
-
-        // Course id.
-        $test = new condition_info((object)array('id' => $page->cmid, 'course' => $course->id));
-        $this->assertDebuggingCalled();
-        $this->assertEquals($realcm, $test->get_full_course_module());
-        $this->assertDebuggingCalled();
-
-        // Full cm.
-        $test = new condition_info($realcm);
-        $this->assertDebuggingCalled();
-        $this->assertEquals($realcm, $test->get_full_course_module());
-        $this->assertDebuggingCalled();
-    }
-
-    /**
-     * Same as above test but for course_sections instead of course_modules.
-     */
-    public function test_section_constructor() {
-        $generator = $this->getDataGenerator();
-        $course = $generator->create_course(
-                array('numsections' => 1), array('createsections' => true));
-        $modinfo = get_fast_modinfo($course);
-
-        // No ID.
-        try {
-            $test = new condition_info_section(((object)array()));
-            $this->fail();
-        } catch (coding_exception $e) {
-            // Do nothing.
-            $this->assertDebuggingCalled();
-        }
-
-        // Get actual cm_info for comparison.
-        $realsection = $modinfo->get_section_info(1);
-
-        // No other data.
-        $test = new condition_info_section((object)array('id' => $realsection->id));
-        $this->assertDebuggingCalled();
-        $this->assertEquals($realsection, $test->get_full_section());
-        $this->assertDebuggingCalled();
-
-        // Course id.
-        $test = new condition_info_section((object)array('id' => $realsection->id,
-                'course' => $course->id));
-        $this->assertDebuggingCalled();
-        $this->assertEquals($realsection, $test->get_full_section());
-        $this->assertDebuggingCalled();
-
-        // Full object.
-        $test = new condition_info_section($realsection);
-        $this->assertDebuggingCalled();
-        $this->assertEquals($realsection, $test->get_full_section());
-        $this->assertDebuggingCalled();
-    }
-
-    /**
-     * Tests the is_available function for modules. This does not test all the
-     * conditions and stuff, because it only needs to check that the system
-     * connects through to the real availability API. Also tests
-     * get_full_information function.
-     */
-    public function test_is_available() {
-        // Create course.
-        $generator = $this->getDataGenerator();
-        $course = $generator->create_course();
-
-        // Create activity with no restrictions and one with date restriction.
-        $page1 = $generator->get_plugin_generator('mod_page')->create_instance(
-                array('course' => $course));
-        $time = time() + 100;
-        $avail = '{"op":"|","show":true,"c":[{"type":"date","d":">=","t":' . $time . '}]}';
-        $page2 = $generator->get_plugin_generator('mod_page')->create_instance(
-                array('course' => $course, 'availability' => $avail));
-
-        // No conditions.
-        $ci = new condition_info((object)array('id' => $page1->cmid),
-                CONDITION_MISSING_EVERYTHING);
-        $this->assertDebuggingCalled();
-        $this->assertTrue($ci->is_available($text, false, 0));
-        $this->assertDebuggingCalled();
-        $this->assertEquals('', $text);
-
-        // Date condition.
-        $ci = new condition_info((object)array('id' => $page2->cmid),
-            CONDITION_MISSING_EVERYTHING);
-        $this->assertDebuggingCalled();
-        $this->assertFalse($ci->is_available($text));
-        $this->assertDebuggingCalled();
-        $expectedtime = userdate($time, get_string('strftimedate', 'langconfig'));
-        $this->assertContains($expectedtime, $text);
-
-        // Full information display.
-        $text = $ci->get_full_information();
-        $this->assertDebuggingCalled();
-        $expectedtime = userdate($time, get_string('strftimedate', 'langconfig'));
-        $this->assertContains($expectedtime, $text);
-    }
-
-    /**
-     * Tests the is_available function for sections.
-     */
-    public function test_section_is_available() {
-        global $DB;
-
-        // Create course.
-        $generator = $this->getDataGenerator();
-        $course = $generator->create_course(
-                array('numsections' => 2), array('createsections' => true));
-
-        // Set one of the sections unavailable.
-        $time = time() + 100;
-        $avail = '{"op":"|","show":true,"c":[{"type":"date","d":">=","t":' . $time . '}]}';
-        $DB->set_field('course_sections', 'availability', $avail, array(
-                'course' => $course->id, 'section' => 2));
-
-        $modinfo = get_fast_modinfo($course);
-
-        // No conditions.
-        $ci = new condition_info_section($modinfo->get_section_info(1));
-        $this->assertDebuggingCalled();
-        $this->assertTrue($ci->is_available($text, false, 0));
-        $this->assertDebuggingCalled();
-        $this->assertEquals('', $text);
-
-        // Date condition.
-        $ci = new condition_info_section($modinfo->get_section_info(2));
-        $this->assertDebuggingCalled();
-        $this->assertFalse($ci->is_available($text));
-        $this->assertDebuggingCalled();
-        $expectedtime = userdate($time, get_string('strftimedate', 'langconfig'));
-        $this->assertContains($expectedtime, $text);
-
-        // Full information display.
-        $text = $ci->get_full_information();
-        $this->assertDebuggingCalled();
-        $expectedtime = userdate($time, get_string('strftimedate', 'langconfig'));
-        $this->assertContains($expectedtime, $text);
-    }
-}
index 004dc8b..180c9d8 100644 (file)
@@ -504,6 +504,45 @@ class core_filelib_testcase extends advanced_testcase {
         $this->assertSame('OK', $contents);
     }
 
+    public function test_curl_protocols() {
+
+        // HTTP and HTTPS requests were verified in previous requests. Now check
+        // that we can selectively disable some protocols.
+        $curl = new curl();
+
+        // Other protocols than HTTP(S) are disabled by default.
+        $testurl = 'file:///';
+        $curl->get($testurl);
+        $this->assertNotEmpty($curl->error);
+        $this->assertEquals(CURLE_UNSUPPORTED_PROTOCOL, $curl->errno);
+
+        $testurl = 'ftp://nowhere';
+        $curl->get($testurl);
+        $this->assertNotEmpty($curl->error);
+        $this->assertEquals(CURLE_UNSUPPORTED_PROTOCOL, $curl->errno);
+
+        $testurl = 'telnet://somewhere';
+        $curl->get($testurl);
+        $this->assertNotEmpty($curl->error);
+        $this->assertEquals(CURLE_UNSUPPORTED_PROTOCOL, $curl->errno);
+
+        // Protocols are also disabled during redirections.
+        $testurl = $this->getExternalTestFileUrl('/test_redir_proto.php');
+        $curl->get($testurl, array('proto' => 'file'));
+        $this->assertNotEmpty($curl->error);
+        $this->assertEquals(CURLE_UNSUPPORTED_PROTOCOL, $curl->errno);
+
+        $testurl = $this->getExternalTestFileUrl('/test_redir_proto.php');
+        $curl->get($testurl, array('proto' => 'ftp'));
+        $this->assertNotEmpty($curl->error);
+        $this->assertEquals(CURLE_UNSUPPORTED_PROTOCOL, $curl->errno);
+
+        $testurl = $this->getExternalTestFileUrl('/test_redir_proto.php');
+        $curl->get($testurl, array('proto' => 'telnet'));
+        $this->assertNotEmpty($curl->error);
+        $this->assertEquals(CURLE_UNSUPPORTED_PROTOCOL, $curl->errno);
+    }
+
     /**
      * Testing prepare draft area
      *
index be11cfd..938366d 100644 (file)
@@ -26,7 +26,6 @@ defined('MOODLE_INTERNAL') || die();
 
 global $CFG;
 require_once($CFG->libdir . '/modinfolib.php');
-require_once($CFG->libdir . '/conditionlib.php');
 
 /**
  * Unit tests for modinfolib.php
@@ -416,88 +415,6 @@ class core_modinfolib_testcase extends advanced_testcase {
         $this->assertNotEquals('Illegal overwriting', $modinfo->cms);
     }
 
-    /**
-     * Test is_user_access_restricted_by_conditional_access()
-     *
-     * The underlying conditional access system is more thoroughly tested in lib/tests/conditionlib_test.php
-     */
-    public function test_is_user_access_restricted_by_conditional_access() {
-        global $DB, $CFG;
-
-        $this->resetAfterTest();
-
-        // Enable conditional availability before creating modules, otherwise the condition data is not written in DB.
-        $CFG->enableavailability = true;
-
-        // Create a course.
-        $course = $this->getDataGenerator()->create_course();
-        // 1. Create an activity that is currently unavailable and hidden entirely (for students).
-        $assign1 = $this->getDataGenerator()->create_module('assign', array('course'=>$course->id),
-                array('availability' => '{"op":"|","show":false,"c":[' .
-                '{"type":"date","d":">=","t":' . (time() + 10000) . '}]}'));
-        // 2. Create an activity that is currently available.
-        $assign2 = $this->getDataGenerator()->create_module('assign', array('course'=>$course->id));
-        // 3. Create an activity that is currently unavailable and set to be greyed out.
-        $assign3 = $this->getDataGenerator()->create_module('assign', array('course'=>$course->id),
-                array('availability' => '{"op":"|","show":true,"c":[' .
-                '{"type":"date","d":">=","t":' . (time() + 10000) . '}]}'));
-
-        // Set up a teacher.
-        $coursecontext = context_course::instance($course->id);
-        $teacherrole = $DB->get_record('role', array('shortname'=>'editingteacher'), '*', MUST_EXIST);
-        $teacher = $this->getDataGenerator()->create_user();
-        role_assign($teacherrole->id, $teacher->id, $coursecontext);
-
-        // If conditional availability is disabled the activity will always be unrestricted.
-        $CFG->enableavailability = false;
-        $cm = get_fast_modinfo($course)->instances['assign'][$assign1->id];
-        $this->assertTrue($cm->uservisible);
-
-        // Test deprecated function.
-        $this->assertFalse($cm->is_user_access_restricted_by_conditional_access());
-        $this->assertEquals(1, count(phpunit_util::get_debugging_messages()));
-        phpunit_util::reset_debugging();
-
-        // Turn on conditional availability and reset the get_fast_modinfo cache.
-        $CFG->enableavailability = true;
-        get_fast_modinfo($course, 0, true);
-
-        // The unavailable, hidden entirely activity should now be restricted.
-        $cm = get_fast_modinfo($course)->instances['assign'][$assign1->id];
-        $this->assertFalse($cm->uservisible);
-        $this->assertFalse($cm->available);
-        $this->assertEquals('', $cm->availableinfo);
-
-        // Test deprecated function.
-        $this->assertTrue($cm->is_user_access_restricted_by_conditional_access());
-        $this->assertEquals(1, count(phpunit_util::get_debugging_messages()));
-        phpunit_util::reset_debugging();
-
-        // If the activity is available it should not be restricted.
-        $cm = get_fast_modinfo($course)->instances['assign'][$assign2->id];
-        $this->assertTrue($cm->uservisible);
-        $this->assertTrue($cm->available);
-
-        // If the activity is unavailable and set to be greyed out it should not be restricted.
-        $cm = get_fast_modinfo($course)->instances['assign'][$assign3->id];
-        $this->assertFalse($cm->uservisible);
-        $this->assertFalse($cm->available);
-        $this->assertNotEquals('', (string)$cm->availableinfo);
-
-        // Test deprecated function (weird case, it actually checks visibility).
-        $this->assertFalse($cm->is_user_access_restricted_by_conditional_access());
-        $this->assertEquals(1, count(phpunit_util::get_debugging_messages()));
-        phpunit_util::reset_debugging();
-
-        // If the activity is unavailable and set to be hidden entirely its restricted unless user has 'moodle/course:viewhiddenactivities'.
-        // Switch to a teacher and reload the context info.
-        $this->setUser($teacher);
-        $this->assertTrue(has_capability('moodle/course:viewhiddenactivities', $coursecontext));
-        $cm = get_fast_modinfo($course)->instances['assign'][$assign1->id];
-        $this->assertTrue($cm->uservisible);
-        $this->assertFalse($cm->available);
-    }
-
     public function test_is_user_access_restricted_by_capability() {
         global $DB;
 
@@ -747,75 +664,6 @@ class core_modinfolib_testcase extends advanced_testcase {
         $this->assertNull($section->availability);
     }
 
-    /**
-     * Some properties have been deprecated from both the section and module
-     * classes. This checks they still work (and show warnings).
-     */
-    public function test_availability_deprecations() {
-        global $CFG, $DB;
-        $this->resetAfterTest();
-        $CFG->enableavailability = true;
-
-        // Create a course with two modules. The modules are not available to
-        // users. One of them is set to show this information, the other is not.
-        // Same setup for sections.
-        $generator = $this->getDataGenerator();
-        $course = $this->getDataGenerator()->create_course(
-                array('format' => 'topics', 'numsections' => 2),
-                array('createsections' => true));
-        $show = '{"op":"|","show":true,"c":[{"type":"date","d":"<","t":1395857332}]}';
-        $noshow = '{"op":"|","show":false,"c":[{"type":"date","d":"<","t":1395857332}]}';
-        $forum1 = $generator->create_module('forum',
-                array('course' => $course->id, 'availability' => $show));
-        $forum2 = $generator->create_module('forum',
-                array('course' => $course->id, 'availability' => $noshow));
-        $DB->set_field('course_sections', 'availability',
-                $show, array('course' => $course->id, 'section' => 1));
-        $DB->set_field('course_sections', 'availability',
-                $noshow, array('course' => $course->id, 'section' => 2));
-
-        // Create a user without special permissions.
-        $user = $generator->create_user();
-        $generator->enrol_user($user->id, $course->id);
-
-        // Get modinfo and cm objects.
-        $modinfo = get_fast_modinfo($course, $user->id);
-        $cm1 = $modinfo->get_cm($forum1->cmid);
-        $cm2 = $modinfo->get_cm($forum2->cmid);
-
-        // Check the showavailability property.
-        $this->assertEquals(1, $cm1->showavailability);
-        $this->assertDebuggingCalled(null, DEBUG_DEVELOPER);
-        $this->assertEquals(0, $cm2->showavailability);
-        $this->assertDebuggingCalled(null, DEBUG_DEVELOPER);
-
-        // Check the dates (these always return 0 now).
-        $this->assertEquals(0, $cm1->availablefrom);
-        $this->assertDebuggingCalled(null, DEBUG_DEVELOPER);
-        $this->assertEquals(0, $cm1->availableuntil);
-        $this->assertDebuggingCalled(null, DEBUG_DEVELOPER);
-
-        // Get section objects.
-        $section1 = $modinfo->get_section_info(1);
-        $section2 = $modinfo->get_section_info(2);
-
-        // Check showavailability.
-        $this->assertEquals(1, $section1->showavailability);
-        $this->assertDebuggingCalled(null, DEBUG_DEVELOPER);
-        $this->assertEquals(0, $section2->showavailability);
-        $this->assertDebuggingCalled(null, DEBUG_DEVELOPER);
-
-        // Check dates (zero).
-        $this->assertEquals(0, $section1->availablefrom);
-        $this->assertDebuggingCalled(null, DEBUG_DEVELOPER);
-                $this->assertEquals(0, $section1->availableuntil);
-        $this->assertDebuggingCalled(null, DEBUG_DEVELOPER);
-
-        // Check groupingid (zero).
-        $this->assertEquals(0, $section1->groupingid);
-        $this->assertDebuggingCalled(null, DEBUG_DEVELOPER);
-    }
-
     /**
      * Tests for get_groups() method.
      */
index 84a93d0..843857b 100644 (file)
     <version>1.10.6</version>
     <licenseversion></licenseversion>
   </library>
-  <library>
-    <location>form</location>
-    <name>MoodleForms</name>
-    <license>GPL</license>
-    <version></version>
-    <licenseversion>2.0+</licenseversion>
-  </library>
   <library>
     <location>html2text.php</location>
     <name>HTML2Text</name>
index f526961..aec6317 100644 (file)
Binary files a/lib/yui/build/moodle-core-checknet/moodle-core-checknet-debug.js and b/lib/yui/build/moodle-core-checknet/moodle-core-checknet-debug.js differ
index f29d349..0dc73e0 100644 (file)
Binary files a/lib/yui/build/moodle-core-checknet/moodle-core-checknet-min.js and b/lib/yui/build/moodle-core-checknet/moodle-core-checknet-min.js differ
index f2e005e..a08ce4d 100644 (file)
Binary files a/lib/yui/build/moodle-core-checknet/moodle-core-checknet.js and b/lib/yui/build/moodle-core-checknet/moodle-core-checknet.js differ
index ee37fb5..42e92db 100644 (file)
@@ -77,6 +77,8 @@ Y.extend(CheckNet, Y.Base, {
     _performCheck: function() {
         Y.io(this.get('uri'), {
             data: {
+                // Add the session key.
+                sesskey: M.cfg.sesskey,
                 // Add a query string to prevent older versions of IE from using the cache.
                 time: new Date().getTime()
             },
index 6fd3bf4..bc10ce0 100644 (file)
@@ -102,7 +102,7 @@ M.core_message.init_defaultoutputs = function(Y) {
                 }, this);
                 parentnode.addClass('dimmed_text');
             } else {
-                parentnode.all('select').each(function(node) {
+                parentnode.all('select[disabled]').each(function(node) {
                     node.removeAttribute('disabled');
                     node.set('value', 'permitted');
                     defaultoutputs.updateCheckboxes(node.ancestor('td'), 0, 0);
index cfa561c..91cb7eb 100644 (file)
@@ -49,6 +49,11 @@ class mod_assign_batch_set_marking_workflow_state_form extends moodleform {
         $options = $params['markingworkflowstates'];
         $mform->addElement('select', 'markingworkflowstate', get_string('markingworkflowstate', 'assign'), $options);
 
+        // Don't allow notification to be sent until in "Released" state.
+        $mform->addElement('selectyesno', 'sendstudentnotifications', get_string('sendstudentnotifications', 'assign'));
+        $mform->setDefault('sendstudentnotifications', 0);
+        $mform->disabledIf('sendstudentnotifications', 'markingworkflowstate', 'neq', ASSIGN_MARKING_WORKFLOW_STATE_RELEASED);
+
         $mform->addElement('hidden', 'id');
         $mform->setType('id', PARAM_INT);
         $mform->addElement('hidden', 'action', 'setbatchmarkingworkflowstate');
@@ -59,5 +64,24 @@ class mod_assign_batch_set_marking_workflow_state_form extends moodleform {
 
     }
 
+    /**
+     * Validate the submitted form data.
+     *
+     * @param array $data array of ("fieldname"=>value) of submitted data
+     * @param array $files array of uploaded files "element_name"=>tmp_file_path
+     * @return array of "element_name"=>"error_description" if there are errors
+     */
+    public function validation($data, $files) {
+        $errors = parent::validation($data, $files);
+
+        // As the implementation of this feature exists currently, no user will see a validation
+        // failure from this form, but this check ensures the form won't validate if someone
+        // manipulates the 'sendstudentnotifications' field's disabled attribute client-side.
+        if (!empty($data['sendstudentnotifications']) && $data['markingworkflowstate'] != ASSIGN_MARKING_WORKFLOW_STATE_RELEASED) {
+            $errors['sendstudentnotifications'] = get_string('studentnotificationworkflowstateerror', 'assign');
+        }
+
+        return $errors;
+    }
 }
 
index 912bd8a..5c60de9 100644 (file)
@@ -44,6 +44,8 @@ class document_services {
     const PAGE_IMAGE_FILEAREA = 'pages';
     /** File area for readonly page images */
     const PAGE_IMAGE_READONLY_FILEAREA = 'readonlypages';
+    /** File area for the stamps */
+    const STAMPS_FILEAREA = 'stamps';
     /** Filename for combined pdf */
     const COMBINED_PDF_FILENAME = 'combined.pdf';
 
@@ -363,6 +365,9 @@ class document_services {
         $record->filepath = '/';
         $fs = \get_file_storage();
 
+        // Remove the existing content of the filearea.
+        $fs->delete_area_files($record->contextid, $record->component, $record->filearea, $record->itemid);
+
         $files = array();
         for ($i = 0; $i < $pagecount; $i++) {
             $image = $pdf->get_image($i);
@@ -421,14 +426,13 @@ class document_services {
         $fs = \get_file_storage();
 
         // If we are after the readonly pages...
-        $copytoreadonly = false;
         if ($readonly) {
             $filearea = self::PAGE_IMAGE_READONLY_FILEAREA;
             if ($fs->is_area_empty($contextid, $component, $filearea, $itemid)) {
-                // We have a problem here, we were supposed to find the files...
-                // let's fallback on the other area, and copy the files to the readonly area.
-                $copytoreadonly = true;
-                $filearea = self::PAGE_IMAGE_FILEAREA;
+                // We have a problem here, we were supposed to find the files.
+                // Attempt to re-generate the pages from the combined images.
+                self::generate_page_images_for_attempt($assignment, $userid, $attemptnumber);
+                self::copy_pages_to_readonly_area($assignment, $grade);
             }
         }
 
@@ -460,10 +464,6 @@ class document_services {
                     $pages[$pagenumber] = $file;
                 }
                 ksort($pages);
-
-                if ($copytoreadonly) {
-                    self::copy_pages_to_readonly_area($assignment, $grade);
-                }
             }
         }
 
index afa01ef..ace0403 100644 (file)
@@ -310,8 +310,9 @@ class page_editor {
     }
 
     /**
-     * This function copies annotations and comments from the source user
-     * to the current group member being processed when using applytoall.
+     * Copy annotations, comments, pages, and other required content from the source user to the current group member
+     * being procssed when using applytoall.
+     *
      * @param int|\assign $assignment
      * @param stdClass $grade
      * @param int $sourceuserid
@@ -327,6 +328,8 @@ class page_editor {
         $sourceusergrade = $assignment->get_user_grade($sourceuserid, true, $grade->attemptnumber);
         $annotations = $DB->get_records('assignfeedback_editpdf_annot', array('gradeid' => $sourceusergrade->id, 'draft' => 1));
         $comments = $DB->get_records('assignfeedback_editpdf_cmnt', array('gradeid' => $sourceusergrade->id, 'draft' => 1));
+        $contextid = $assignment->get_context()->id;
+        $sourceitemid = $sourceusergrade->id;
 
         // Add annotations and comments to current user to generate feedback file.
         foreach ($annotations as $annotation) {
@@ -338,24 +341,42 @@ class page_editor {
             $DB->insert_record('assignfeedback_editpdf_cmnt', $comment);
         }
 
-        // Delete the existing stamps and copy the source ones.
         $fs = get_file_storage();
-        $fs->delete_area_files($assignment->get_context()->id, 'assignfeedback_editpdf', 'stamps', $grade->id);
-        if ($files = $fs->get_area_files($assignment->get_context()->id,
-                                         'assignfeedback_editpdf',
-                                         'stamps',
-                                         $sourceusergrade->id,
-                                         "filename",
-                                         false)) {
+
+        // Copy the stamp files.
+        self::replace_files_from_to($fs, $contextid, $sourceitemid, $grade->id, document_services::STAMPS_FILEAREA, true);
+
+        // Copy the PAGE_IMAGE_FILEAREA files.
+        self::replace_files_from_to($fs, $contextid, $sourceitemid, $grade->id, document_services::PAGE_IMAGE_FILEAREA);
+
+        return true;
+    }
+
+    /**
+     * Replace the area files in the specified area with those in the source item id.
+     *
+     * @param \file_storage $fs The file storage class
+     * @param int $contextid The ID of the context for the assignment.
+     * @param int $sourceitemid The itemid to copy from - typically the source grade id.
+     * @param int $itemid The itemid to copy to - typically the target grade id.
+     * @param string $area The file storage area.
+     * @param bool $includesubdirs Whether to copy the content of sub-directories too.
+     */
+    public static function replace_files_from_to($fs, $contextid, $sourceitemid, $itemid, $area, $includesubdirs = false) {
+        $component = 'assignfeedback_editpdf';
+        // Remove the existing files within this area.
+        $fs->delete_area_files($contextid, $component, $area, $itemid);
+
+        // Copy the files from the source area.
+        if ($files = $fs->get_area_files($contextid, $component, $area, $sourceitemid,
+                                         "filename", $includesubdirs)) {
             foreach ($files as $file) {
                 $newrecord = new \stdClass();
-                $newrecord->contextid = $assignment->get_context()->id;
-                $newrecord->itemid = $grade->id;
+                $newrecord->contextid = $contextid;
+                $newrecord->itemid = $itemid;
                 $fs->create_file_from_storedfile($newrecord, $file);
             }
         }
-
-        return true;
     }
 
     /**
diff --git a/mod/assign/feedback/editpdf/tests/behat/group_annotations.feature b/mod/assign/feedback/editpdf/tests/behat/group_annotations.feature
new file mode 100644 (file)
index 0000000..1bdb027
--- /dev/null
@@ -0,0 +1,74 @@
+@mod @mod_assign @assignfeedback @assignfeedback_editpdf @_file_upload
+Feature: In a group assignment, teacher can annotate PDF files for all users
+  In order to provide visual report on a graded PDF for all users
+  As a teacher
+  I need to use the PDF editor for a group assignment
+
+  @javascript
+  Scenario: Submit a PDF file as a student and annotate the PDF as a teacher
+    Given ghostscript is installed
+    And the following "courses" exist:
+      | fullname | shortname | category | groupmode |
+      | Course 1 | C1 | 0 | 1 |
+    And 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 "course enrolments" exist:
+      | user | course | role |
+      | teacher1 | C1 | editingteacher |
+      | student1 | C1 | student |
+      | student2 | C1 | student |
+    And the following "groups" exist:
+      | name     | course | idnumber |
+      | G1       | C1     | G1       |
+    And the following "group members" exist:
+      | user     | group |
+      | student1 | G1    |
+      | student2 | G1    |
+    And I log in as "teacher1"
+    And I follow "Course 1"
+    And I turn editing mode on
+    And I add a "Assignment" to section "1" and I fill the form with:
+      | Assignment name                   | Test assignment name |
+      | Description                       | Submit your PDF file |
+      | assignsubmission_file_enabled     | 1 |
+      | Maximum number of uploaded files  | 1 |
+      | Students submit in groups         | Yes |
+    And I log out
+    And I log in as "student1"
+    And I follow "Course 1"
+    And I follow "Test assignment name"
+    And I press "Add submission"
+    And I upload "mod/assign/feedback/editpdf/tests/fixtures/submission.pdf" file to "File submissions" filemanager
+    And I press "Save changes"
+    And I should see "Submitted for grading"
+    And I should see "submission.pdf"
+    And I should see "Not graded"
+    And I log out
+    And I log in as "teacher1"
+    And I follow "Course 1"
+    And I follow "Test assignment name"
+    And I follow "View/grade all submissions"
+    And I click on "Grade" "link" in the "Submitted for grading" "table_row"
+    And I follow "Launch PDF editor..."
+    And I click on ".navigate-next-button" "css_element"
+    And I click on ".stampbutton" "css_element"
+    And I click on ".drawingcanvas" "css_element"
+    And I click on "Close" "button"
+    And I press "Save changes"
+    And I should see "The grade changes were saved"
+    And I log out
+    And I log in as "student1"
+    And I follow "Course 1"
+    And I follow "Test assignment name"
+    When I follow "View annotated PDF..."
+    Then I should see "Annotate PDF"
+    And I click on "Close" "button"
+    And I log out
+    And I log in as "student2"
+    And I follow "Course 1"
+    And I follow "Test assignment name"
+    And I follow "View annotated PDF..."
+    And I should see "Annotate PDF"
index 751d220..2aab572 100644 (file)
@@ -77,6 +77,12 @@ class mod_assign_grade_form extends moodleform {
         global $DB;
         $errors = parent::validation($data, $files);
         $instance = $this->assignment->get_instance();
+
+        if ($instance->markingworkflow && !empty($data['sendstudentnotifications']) &&
+                $data['workflowstate'] != ASSIGN_MARKING_WORKFLOW_STATE_RELEASED) {
+            $errors['sendstudentnotifications'] = get_string('studentnotificationworkflowstateerror', 'assign');
+        }
+
         // Advanced grading.
         if (!array_key_exists('grade', $data)) {
             return $errors;
index 6652b18..da89afc 100644 (file)
@@ -353,6 +353,7 @@ $string['setmarkerallocationforlog'] = 'Set marking allocation : (id={$a->id}, f
 $string['settings'] = 'Assignment settings';
 $string['showrecentsubmissions'] = 'Show recent submissions';
 $string['status'] = 'Status';
+$string['studentnotificationworkflowstateerror'] = 'Marking workflow state must be \'Released\' to notify students.';
 $string['submissioncopiedtext'] = 'You have made a copy of your previous
 assignment submission for \'{$a->assignment}\'
 
index d79fbce..6799d54 100644 (file)
@@ -404,7 +404,8 @@ function assign_print_overview($courses, &$htmlarray) {
                                                   g.attemptnumber = s.attemptnumber
                                               WHERE
                                                   ( g.timemodified is NULL OR
-                                                  s.timemodified > g.timemodified ) AND
+                                                  s.timemodified > g.timemodified OR
+                                                  g.grade IS NULL ) AND
                                                   s.timemodified IS NOT NULL AND
                                                   s.status = ? AND
                                                   s.latest = 1 AND
index e617070..e96aaed 100644 (file)
@@ -139,6 +139,9 @@ class assign {
     /** @var array cached list of participants for this assignment. The cache key will be group, showactive and the context id */
     private $participants = array();
 
+    /** @var array cached list of user groups when team submissions are enabled. The cache key will be the user. */
+    private $usersubmissiongroups = array();
+
     /**
      * Constructor for the base assign class.
      *
@@ -1380,6 +1383,11 @@ class assign {
      * @return array List of user records
      */
     public function list_participants($currentgroup, $idsonly) {
+
+        if (empty($currentgroup)) {
+            $currentgroup = 0;
+        }
+
         $key = $this->context->id . '-' . $currentgroup . '-' . $this->show_only_active_users();
         if (!isset($this->participants[$key])) {
             $users = get_enrolled_users($this->context, 'mod/assign:submit', $currentgroup, 'u.*', null, null, null,
@@ -1406,21 +1414,51 @@ class assign {
     /**
      * Load a count of valid teams for this assignment.
      *
+     * @param int $activitygroup Activity active group
      * @return int number of valid teams
      */
-    public function count_teams() {
+    public function count_teams($activitygroup = 0) {
 
-        $groups = groups_get_all_groups($this->get_course()->id,
-                                        0,
-                                        $this->get_instance()->teamsubmissiongroupingid,
-                                        'g.id');
-        $count = count($groups);
+        $count = 0;
 
-        // See if there are any users in the default group.
-        $defaultusers = $this->get_submission_group_members(0, true);
-        if (count($defaultusers) > 0) {
-            $count += 1;
+        $participants = $this->list_participants($activitygroup, true);
+
+        // If a team submission grouping id is provided all good as all returned groups
+        // are the submission teams, but if no team submission grouping was specified
+        // $groups will contain all participants groups.
+        if ($this->get_instance()->teamsubmissiongroupingid) {
+
+            // We restrict the users to the selected group ones.
+            $groups = groups_get_all_groups($this->get_course()->id,
+                                            array_keys($participants),
+                                            $this->get_instance()->teamsubmissiongroupingid,
+                                            'DISTINCT g.id, g.name');
+
+            $count = count($groups);
+
+            // When a specific group is selected we don't count the default group users.
+            if ($activitygroup == 0) {
+
+                // See if there are any users in the default group.
+                $defaultusers = $this->get_submission_group_members(0, true);
+                if (count($defaultusers) > 0) {
+                    $count += 1;
+                }
+            }
+        } else {
+            // It is faster to loop around participants if no grouping was specified.
+            $groups = array();
+            foreach ($participants as $participant) {
+                if ($group = $this->get_submission_group($participant->id)) {
+                    $groups[$group->id] = true;
+                } else {
+                    $groups[0] = true;
+                }
+            }
+
+            $count = count($groups);
         }
+
         return $count;
     }
 
@@ -1468,7 +1506,7 @@ class assign {
                         s.assignment = :assignid AND
                         s.timemodified IS NOT NULL AND
                         s.status = :submitted AND
-                        (s.timemodified > g.timemodified OR g.timemodified IS NULL)';
+                        (s.timemodified > g.timemodified OR g.timemodified IS NULL OR g.grade IS NULL)';
 
         return $DB->count_records_sql($sql, $params);
     }
@@ -1568,13 +1606,27 @@ class assign {
         $params['submissionstatus'] = $status;
 
         if ($this->get_instance()->teamsubmission) {
+
+            $groupsstr = '';
+            if ($currentgroup != 0) {
+                // If there is an active group we should only display the current group users groups.
+                $participants = $this->list_participants($currentgroup, true);
+                $groups = groups_get_all_groups($this->get_course()->id,
+                                                array_keys($participants),
+                                                $this->get_instance()->teamsubmissiongroupingid,
+                                                'DISTINCT g.id, g.name');
+                list($groupssql, $groupsparams) = $DB->get_in_or_equal(array_keys($groups), SQL_PARAMS_NAMED);
+ &nbs