Merge branch 'MDL-51217-master' of git://github.com/lucisgit/moodle
authorDavid Monllao <davidm@moodle.com>
Mon, 14 Sep 2015 02:04:08 +0000 (10:04 +0800)
committerDavid Monllao <davidm@moodle.com>
Mon, 14 Sep 2015 02:04:08 +0000 (10:04 +0800)
329 files changed:
admin/index.php
admin/roles/ajax.php [new file with mode: 0644]
admin/roles/classes/capability_table_base.php
admin/roles/classes/capability_table_with_risks.php
admin/roles/classes/check_capability_table.php
admin/roles/classes/define_role_table_advanced.php
admin/roles/classes/define_role_table_basic.php
admin/roles/classes/override_permissions_table_advanced.php
admin/roles/classes/permissions_table.php
admin/roles/classes/view_role_definition_table.php
admin/roles/permissions.php
admin/tool/behat/tests/behat/data_generators.feature
admin/tool/customlang/edit.php
admin/tool/customlang/renderer.php
admin/tool/health/lang/en/tool_health.php
admin/tool/installaddon/classes/validator.php
admin/tool/installaddon/lang/en/tool_installaddon.php
admin/tool/installaddon/tests/fixtures/nocomponent/baz/lang/en/auth_baz.php [new file with mode: 0644]
admin/tool/installaddon/tests/fixtures/nocomponent/baz/version.php [new file with mode: 0644]
admin/tool/installaddon/tests/fixtures/nolang/bah/version.php
admin/tool/installaddon/tests/fixtures/plugindir/foobar/version.php
admin/tool/installaddon/tests/fixtures/plugindir/legacymod/lang/en/legacymod.php [new file with mode: 0644]
admin/tool/installaddon/tests/fixtures/plugindir/legacymod/version.php [new file with mode: 0644]
admin/tool/installaddon/tests/fixtures/wronglang/bah/lang/en/bah.php [new file with mode: 0644]
admin/tool/installaddon/tests/fixtures/wronglang/bah/version.php [new file with mode: 0644]
admin/tool/installaddon/tests/validator_test.php
admin/tool/installaddon/version.php
admin/tool/langimport/lang/en/tool_langimport.php
admin/tool/langimport/tests/behat/manage_langpacks.feature
admin/tool/task/cli/schedule_task.php
auth/cas/auth.php
blocks/messages/block_messages.php
blocks/tag_youtube/lang/en/block_tag_youtube.php
blocks/tags/block_tags.php
blocks/tags/coursetags.js [deleted file]
blocks/tags/lang/en/block_tags.php
blocks/tags/lang/en/deprecated.txt [new file with mode: 0644]
blocks/tags/styles.css [deleted file]
blocks/tags/tests/behat/coursetags.feature [deleted file]
blocks/tests/behat/configure_block_throughout_site.feature
completion/classes/external.php
completion/cron.php
config-dist.php
course/edit.php
course/edit_form.php
course/lib.php
course/renderer.php
course/tags.php [new file with mode: 0644]
course/tags_form.php [new file with mode: 0644]
course/tests/behat/coursetags.feature [new file with mode: 0644]
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/meta/classes/observer.php
enrol/meta/locallib.php
enrol/meta/tests/plugin_test.php
files/tests/behat/add_custom_file_type.feature [new file with mode: 0644]
files/tests/fixtures/custom_filetype.mdlr [new file with mode: 0644]
grade/edit/tree/index.php
grade/edit/tree/lib.php
grade/lib.php
grade/report/grader/lib.php
grade/report/singleview/lib.php
grade/report/user/lib.php
grade/tests/behat/behat_grade.php
grade/tests/behat/grade_aggregation.feature
grade/tests/behat/grade_aggregation_changes.feature
grade/tests/behat/grade_average.feature
grade/tests/behat/grade_calculated_grade_items.feature
grade/tests/behat/grade_calculated_grade_items_20150627.feature
grade/tests/behat/grade_calculated_weights.feature
grade/tests/behat/grade_contribution_with_extra_credit.feature
grade/tests/behat/grade_mingrade.feature
grade/tests/behat/grade_minmax.feature
grade/tests/behat/grade_natural_exclude_empty.feature
grade/tests/behat/grade_natural_exclude_empty_20150619.feature
grade/tests/behat/grade_natural_normalisation.feature
grade/tests/behat/grade_natural_normalisation_20150619.feature
grade/tests/behat/grade_point_maximum.feature
grade/tests/behat/grade_scales.feature
grade/tests/behat/grade_scales_aggregation.feature
grade/tests/behat/grade_single_item_scales.feature
grade/tests/behat/grade_view.feature
grade/tests/edittreelib_test.php
group/overview.php
install/lang/fr_ca/install.php
install/lang/xct/langconfig.php [moved from blocks/tags/settings.php with 60% similarity]
lang/en/admin.php
lang/en/deprecated.txt
lang/en/error.php
lang/en/grades.php
lang/en/hub.php
lang/en/media.php
lang/en/message.php
lang/en/moodle.php
lang/en/question.php
lang/en/role.php
lang/en/tag.php
lib/adminlib.php
lib/ajax/blocks.php
lib/amd/build/permissionmanager.min.js [new file with mode: 0644]
lib/amd/build/tag.min.js [new file with mode: 0644]
lib/amd/build/templates.min.js
lib/amd/src/permissionmanager.js [new file with mode: 0644]
lib/amd/src/tag.js [new file with mode: 0644]
lib/amd/src/templates.js
lib/behat/form_field/behat_form_field.php
lib/blocklib.php
lib/classes/plugin_manager.php
lib/classes/session/manager.php
lib/classes/task/completion_daily_task.php [moved from lib/classes/task/completion_cron_task.php with 77% similarity]
lib/classes/task/completion_regular_task.php [new file with mode: 0644]
lib/classes/useragent.php
lib/cronlib.php
lib/db/access.php
lib/db/install.xml
lib/db/services.php
lib/db/tasks.php
lib/db/upgrade.php
lib/db/upgradelib.php
lib/deprecatedlib.php
lib/dml/mssql_native_moodle_database.php
lib/dml/sqlsrv_native_moodle_database.php
lib/editor/atto/autosave-ajax.php
lib/editor/atto/classes/task/autosave_cleanup_task.php [new file with mode: 0644]
lib/editor/atto/db/tasks.php [new file with mode: 0644]
lib/editor/atto/lang/en/editor_atto.php
lib/editor/atto/lib.php
lib/editor/atto/tests/behat/autosave.feature [new file with mode: 0644]
lib/editor/atto/version.php
lib/editor/tinymce/lib.php
lib/editorlib.php
lib/externallib.php
lib/form/editor.php
lib/formslib.php
lib/grade/grade_category.php
lib/moodlelib.php
lib/myprofilelib.php
lib/navigationlib.php
lib/outputlib.php
lib/outputrenderers.php
lib/phpunit/classes/util.php
lib/rsslib.php
lib/setuplib.php
lib/templates/permissionmanager_panelcontent.mustache [new file with mode: 0644]
lib/templates/permissionmanager_role.mustache [new file with mode: 0644]
lib/tests/behat/behat_forms.php
lib/tests/behat/behat_general.php
lib/tests/moodlelib_test.php
lib/tests/other/completion.manualtest.txt
lib/tests/setuplib_test.php
lib/tests/upgradelib_test.php
lib/tests/useragent_test.php
lib/upgrade.txt
lib/upgradelib.php
lib/yui/build/moodle-core-actionmenu/moodle-core-actionmenu-debug.js
lib/yui/build/moodle-core-actionmenu/moodle-core-actionmenu-min.js
lib/yui/build/moodle-core-actionmenu/moodle-core-actionmenu.js
lib/yui/build/moodle-core-dock/moodle-core-dock-debug.js
lib/yui/build/moodle-core-dock/moodle-core-dock-min.js
lib/yui/build/moodle-core-dock/moodle-core-dock.js
lib/yui/build/moodle-core-event/moodle-core-event-debug.js
lib/yui/build/moodle-core-event/moodle-core-event-min.js
lib/yui/build/moodle-core-event/moodle-core-event.js
lib/yui/src/actionmenu/js/actionmenu.js
lib/yui/src/dock/js/block.js
lib/yui/src/dock/js/dock.js
lib/yui/src/event/js/event.js
login/signup.php
login/signup_form.php
message/index.php
message/lib.php
message/tests/behat/delete_messages.feature [new file with mode: 0644]
message/yui/build/moodle-core_message-toolbox/moodle-core_message-toolbox-debug.js [new file with mode: 0644]
message/yui/build/moodle-core_message-toolbox/moodle-core_message-toolbox-min.js [new file with mode: 0644]
message/yui/build/moodle-core_message-toolbox/moodle-core_message-toolbox.js [new file with mode: 0644]
message/yui/src/toolbox/build.json [new file with mode: 0644]
message/yui/src/toolbox/js/delete.js [new file with mode: 0644]
message/yui/src/toolbox/meta/toolbox.json [new file with mode: 0644]
mod/assign/lang/en/assign.php
mod/assign/lib.php
mod/assign/tests/lib_test.php
mod/book/classes/external.php [new file with mode: 0644]
mod/book/db/services.php [new file with mode: 0644]
mod/book/lang/en/book.php
mod/book/lib.php
mod/book/tests/externallib_test.php [new file with mode: 0644]
mod/book/tests/lib_test.php
mod/book/version.php
mod/book/view.php
mod/chat/chat_ajax.php
mod/chat/classes/external.php [new file with mode: 0644]
mod/chat/db/services.php [new file with mode: 0644]
mod/chat/lang/en/chat.php
mod/chat/lib.php
mod/chat/tests/externallib_test.php [new file with mode: 0644]
mod/chat/version.php
mod/chat/view.php
mod/choice/classes/external.php [new file with mode: 0644]
mod/choice/db/services.php [new file with mode: 0644]
mod/choice/lang/en/choice.php
mod/choice/lib.php
mod/choice/tests/externallib_test.php [new file with mode: 0644]
mod/choice/tests/lib_test.php [new file with mode: 0644]
mod/choice/version.php
mod/choice/view.php
mod/data/field/textarea/field.class.php
mod/data/lib.php
mod/data/templates.php
mod/data/tests/lib_test.php
mod/forum/classes/post_form.php
mod/forum/db/services.php
mod/forum/externallib.php
mod/forum/lib.php
mod/forum/post.php
mod/forum/tests/behat/groups_in_course_no_groups_in_forum.feature [new file with mode: 0644]
mod/forum/tests/behat/no_groups_in_course.feature [new file with mode: 0644]
mod/forum/tests/behat/separate_group_discussions.feature
mod/forum/tests/behat/separate_group_single_group_discussions.feature
mod/forum/tests/behat/visible_group_discussions.feature [new file with mode: 0644]
mod/forum/tests/externallib_test.php
mod/forum/tests/lib_test.php
mod/forum/version.php
mod/imscp/classes/external.php [new file with mode: 0644]
mod/imscp/db/services.php [new file with mode: 0644]
mod/imscp/lib.php
mod/imscp/tests/externallib_test.php [new file with mode: 0644]
mod/imscp/tests/lib_test.php
mod/imscp/version.php
mod/imscp/view.php
mod/lesson/continue.php
mod/lesson/lang/en/lesson.php
mod/lesson/locallib.php
mod/lesson/mod_form.php
mod/lesson/tests/behat/lesson_navigation.feature
mod/lesson/tests/behat/lesson_question_attempts.feature [new file with mode: 0644]
mod/lesson/view.php
mod/quiz/attemptlib.php
mod/quiz/review.php
mod/quiz/reviewquestion.php
mod/quiz/tests/behat/completion_condition_attempts_used.feature
mod/quiz/tests/behat/completion_condition_passing_grade.feature
mod/quiz/tests/behat/manually_mark_question.feature [new file with mode: 0644]
mod/quiz/yui/build/moodle-mod_quiz-toolboxes/moodle-mod_quiz-toolboxes-debug.js
mod/quiz/yui/build/moodle-mod_quiz-toolboxes/moodle-mod_quiz-toolboxes-min.js
mod/quiz/yui/build/moodle-mod_quiz-toolboxes/moodle-mod_quiz-toolboxes.js
mod/quiz/yui/src/toolboxes/js/resource.js
mod/scorm/lang/en/scorm.php
mod/upgrade.txt
mod/wiki/pagelib.php
mod/wiki/tests/behat/edit_tags.feature
notes/externallib.php
pix/i/checked.png [new file with mode: 0644]
pix/i/checked.svg [new file with mode: 0644]
pix/i/unchecked.png [new file with mode: 0644]
pix/i/unchecked.svg [new file with mode: 0644]
question/behaviour/behaviourbase.php
question/behaviour/manualgraded/tests/walkthrough_test.php
question/behaviour/rendererbase.php
question/engine/lib.php
question/engine/questionattempt.php
question/engine/tests/questionattempt_test.php
question/engine/tests/questionattempt_with_steps_test.php
question/engine/tests/questionutils_test.php
question/engine/upgrade.txt
question/type/essay/renderer.php
rating/classes/external.php
rating/index.php
rating/tests/externallib_test.php
repository/youtube/lang/en/repository_youtube.php
tag/classes/external.php [new file with mode: 0644]
tag/classes/manage_table.php [new file with mode: 0644]
tag/classes/output/tag.php [new file with mode: 0644]
tag/coursetags_add.php [deleted file]
tag/coursetags_edit.php [deleted file]
tag/coursetags_more.php [deleted file]
tag/coursetagslib.php [deleted file]
tag/edit.php
tag/edit_form.php
tag/index.php
tag/lib.php
tag/manage.php
tag/tag.js
tag/templates/tagflag.mustache [new file with mode: 0644]
tag/templates/tagname.mustache [new file with mode: 0644]
tag/templates/tagtype.mustache [new file with mode: 0644]
tag/tests/behat/delete_tag.feature [new file with mode: 0644]
tag/tests/behat/edit_tag.feature [new file with mode: 0644]
tag/tests/behat/flag_tags.feature [new file with mode: 0644]
tag/tests/behat/official_tags.feature [new file with mode: 0644]
tag/tests/events_test.php
tag/tests/external_test.php [new file with mode: 0644]
tag/tests/taglib_test.php
tag/upgrade.txt
theme/base/style/core.css
theme/base/style/course.css
theme/base/style/message.css
theme/bootstrapbase/layout/columns1.php
theme/bootstrapbase/layout/columns2.php
theme/bootstrapbase/layout/columns3.php
theme/bootstrapbase/layout/popup.php
theme/bootstrapbase/layout/secure.php
theme/bootstrapbase/less/moodle.less
theme/bootstrapbase/less/moodle/bootstrapoverride.less
theme/bootstrapbase/less/moodle/core.less
theme/bootstrapbase/less/moodle/course.less
theme/bootstrapbase/less/moodle/dock.less
theme/bootstrapbase/less/moodle/grade.less
theme/bootstrapbase/less/moodle/message.less
theme/bootstrapbase/less/moodle/responsive.less
theme/bootstrapbase/renderers/core_renderer.php
theme/bootstrapbase/style/moodle.css
theme/clean/classes/core_renderer.php
theme/clean/lang/en/theme_clean.php
theme/clean/layout/columns1.php
theme/clean/layout/columns2.php
theme/clean/layout/columns3.php
theme/clean/layout/secure.php
theme/clean/lib.php
theme/image.php
theme/more/lang/en/theme_more.php
theme/upgrade.txt
user/lib.php
user/profile.php
user/tests/behat/view_full_profile.feature
user/tests/userlib_test.php
user/view.php
version.php

index bad3ed9..1cea8f8 100644 (file)
@@ -513,7 +513,15 @@ if (isguestuser()) {
     redirect(get_login_url());
 }
 $context = context_system::instance();
-require_capability('moodle/site:config', $context);
+
+if (!has_capability('moodle/site:config', $context)) {
+    // Do not throw exception display an empty page with administration menu if visible for current user.
+    $PAGE->set_title($SITE->fullname);
+    $PAGE->set_heading($SITE->fullname);
+    echo $OUTPUT->header();
+    echo $OUTPUT->footer();
+    exit;
+}
 
 // check that site is properly customized
 $site = get_site();
diff --git a/admin/roles/ajax.php b/admin/roles/ajax.php
new file mode 100644 (file)
index 0000000..717b8f2
--- /dev/null
@@ -0,0 +1,81 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * This file processes AJAX requests and returns JSON
+ *
+ * This is a server part of yui permissions manager module
+ *
+ * @package core_role
+ * @copyright 2015 Martin Mastny
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+define('AJAX_SCRIPT', true);
+
+require(__DIR__ . '/../../config.php');
+
+$contextid = required_param('contextid', PARAM_INT);
+$getroles = optional_param('getroles', 0, PARAM_BOOL);
+
+list($context, $course, $cm) = get_context_info_array($contextid);
+
+require_login($course, false, $cm);
+require_capability('moodle/role:review', $context);
+require_sesskey();
+
+list($overridableroles, $overridecounts, $nameswithcounts) = get_overridable_roles($context,
+        ROLENAME_BOTH, true);
+
+if ($getroles) {
+    echo json_encode($overridableroles);
+    die();
+}
+
+$capability = required_param('capability', PARAM_CAPABILITY);
+$roleid = required_param('roleid', PARAM_INT);
+$action = required_param('action', PARAM_ALPHA);
+
+$capability = $DB->get_record('capabilities', array('name' => $capability), '*', MUST_EXIST);
+
+if (!isset($overridableroles[$roleid])) {
+    throw new moodle_exception('invalidarguments');
+}
+
+if (!has_capability('moodle/role:override', $context)) {
+    if (!has_capability('moodle/role:safeoverride', $context) || !is_safe_capability($capability)) {
+        require_capability('moodle/role:override', $context);
+    }
+}
+
+switch ($action) {
+    case 'allow':
+        role_change_permission($roleid, $context, $capability->name, CAP_ALLOW);
+        break;
+    case 'prevent':
+        role_change_permission($roleid, $context, $capability->name, CAP_PREVENT);
+        break;
+    case 'prohibit':
+        role_change_permission($roleid, $context, $capability->name, CAP_PROHIBIT);
+        break;
+    case 'unprohibit':
+        role_change_permission($roleid, $context, $capability->name, CAP_INHERIT);
+        break;
+    default:
+        throw new moodle_exception('invalidarguments');
+}
+
+echo json_encode($action);
+die();
\ No newline at end of file
index b46c137..2765659 100644 (file)
@@ -106,18 +106,22 @@ abstract class core_role_capability_table_base {
             $component = $capability->component;
 
             // Start the row.
-            echo '<tr class="' . implode(' ', array_unique(array_merge(array('rolecap'),
-                    $this->get_row_classes($capability)))) . '">';
+            $rowattributes = $this->get_row_attributes($capability);
+            // Handle class attributes same as other.
+            $rowclasses = array_unique(array_merge(array('rolecap'), $this->get_row_classes($capability)));
+            if (array_key_exists('class', $rowattributes)) {
+                $rowclasses = array_unique(array_merge($rowclasses, array($rowattributes['class'])));
+            }
+            $rowattributes['class']  = implode(' ', $rowclasses);
 
             // Table cell for the capability name.
-            echo '<th scope="row" class="name"><span class="cap-desc">' . get_capability_docs_link($capability) .
+            $contents = '<th scope="row" class="name"><span class="cap-desc">' . get_capability_docs_link($capability) .
                 '<span class="cap-name">' . $capability->name . '</span></span></th>';
 
             // Add the cells specific to this table.
-            $this->add_row_cells($capability);
+            $contents .= $this->add_row_cells($capability);
 
-            // End the row.
-            echo "</tr>\n";
+            echo html_writer::tag('tr', $contents, $rowattributes);
         }
 
         // End of the table.
@@ -167,6 +171,17 @@ abstract class core_role_capability_table_base {
         return array();
     }
 
+    /**
+     * For subclasses to override. Additional attributes to be added to
+     * each table row for the capability
+     *
+     * @param stdClass $capability the capability this row relates to.
+     * @return array attribute names and their values.
+     */
+    protected function get_row_attributes($capability) {
+        return array();
+    }
+
     /**
      * For subclasses to override. Output the data cells for this capability. The
      * capability name cell will already have been output.
@@ -174,6 +189,7 @@ abstract class core_role_capability_table_base {
      * You can rely on get_row_classes always being called before add_row_cells.
      *
      * @param stdClass $capability the capability this row relates to.
+     * @return string html of row cells
      */
     protected abstract function add_row_cells($capability);
 }
index 5fd57dd..acf7257 100644 (file)
@@ -163,15 +163,16 @@ abstract class core_role_capability_table_with_risks extends core_role_capabilit
     protected abstract function add_permission_cells($capability);
 
     protected function add_row_cells($capability) {
-        $this->add_permission_cells($capability);
+        $cells = $this->add_permission_cells($capability);
         // One cell for each possible risk.
         foreach ($this->allrisks as $riskname => $risk) {
-            echo '<td class="risk ' . str_replace('risk', '', $riskname) . '">';
+            $cells .= '<td class="risk ' . str_replace('risk', '', $riskname) . '">';
             if ($risk & (int)$capability->riskbitmask) {
-                echo $this->get_risk_icon($riskname);
+                $cells .= $this->get_risk_icon($riskname);
             }
-            echo '</td>';
+            $cells .= '</td>';
         }
+        return $cells;
     }
 
     /**
index 20f9370..b716ae5 100644 (file)
@@ -79,6 +79,6 @@ class core_role_check_capability_table extends core_role_capability_table_base {
         $a->fullname = $this->fullname;
         $a->capability = $capability->name;
         $a->context = $this->contextname;
-        echo '<td>' . $result . '</td>';
+        return '<td>' . $result . '</td>';
     }
 }
index 8b4cc51..06f565d 100644 (file)
@@ -628,6 +628,7 @@ class core_role_define_role_table_advanced extends core_role_capability_table_wi
 
     protected function add_permission_cells($capability) {
         // One cell for each possible permission.
+        $content = '';
         foreach ($this->displaypermissions as $perm => $permname) {
             $strperm = $this->strperms[$permname];
             $extraclass = '';
@@ -638,11 +639,12 @@ class core_role_define_role_table_advanced extends core_role_capability_table_wi
             if ($this->permissions[$capability->name] == $perm) {
                 $checked = 'checked="checked" ';
             }
-            echo '<td class="' . $permname . $extraclass . '">';
-            echo '<label><input type="radio" name="' . $capability->name .
+            $content .= '<td class="' . $permname . $extraclass . '">';
+            $content .= '<label><input type="radio" name="' . $capability->name .
                 '" value="' . $perm . '" ' . $checked . '/> ';
-            echo '<span class="note">' . $strperm . '</span>';
-            echo '</label></td>';
+            $content .= '<span class="note">' . $strperm . '</span>';
+            $content .= '</label></td>';
         }
+        return $content;
     }
 }
index 14393e7..0c3d692 100644 (file)
@@ -45,19 +45,20 @@ class core_role_define_role_table_basic extends core_role_define_role_table_adva
         $perm = $this->permissions[$capability->name];
         $permname = $this->allpermissions[$perm];
         $defaultperm = $this->allpermissions[$this->parentpermissions[$capability->name]];
-        echo '<td class="' . $permname . '">';
+        $content = '<td class="' . $permname . '">';
         if ($perm == CAP_ALLOW || $perm == CAP_INHERIT) {
             $checked = '';
             if ($perm == CAP_ALLOW) {
                 $checked = 'checked="checked" ';
             }
-            echo '<input type="hidden" name="' . $capability->name . '" value="' . CAP_INHERIT . '" />';
-            echo '<label><input type="checkbox" name="' . $capability->name .
+            $content .= '<input type="hidden" name="' . $capability->name . '" value="' . CAP_INHERIT . '" />';
+            $content .= '<label><input type="checkbox" name="' . $capability->name .
                 '" value="' . CAP_ALLOW . '" ' . $checked . '/> ' . $this->strallow . '</label>';
         } else {
-            echo '<input type="hidden" name="' . $capability->name . '" value="' . $perm . '" />';
-            echo $this->strperms[$permname] . '<span class="note">' . $this->stradvmessage . '</span>';
+            $content .= '<input type="hidden" name="' . $capability->name . '" value="' . $perm . '" />';
+            $content .= $this->strperms[$permname] . '<span class="note">' . $this->stradvmessage . '</span>';
         }
-        echo '</td>';
+        $content .= '</td>';
+        return $content;
     }
 }
index 7da1239..a872b96 100644 (file)
@@ -73,6 +73,7 @@ class core_role_override_permissions_table_advanced extends core_role_capability
         }
 
         // One cell for each possible permission.
+        $content = '';
         foreach ($this->displaypermissions as $perm => $permname) {
             $strperm = $this->strperms[$permname];
             $extraclass = '';
@@ -83,8 +84,8 @@ class core_role_override_permissions_table_advanced extends core_role_capability
             if ($this->permissions[$capability->name] == $perm) {
                 $checked = 'checked="checked" ';
             }
-            echo '<td class="' . $permname . $extraclass . '">';
-            echo '<label><input type="radio" name="' . $capability->name .
+            $content .= '<td class="' . $permname . $extraclass . '">';
+            $content .= '<label><input type="radio" name="' . $capability->name .
                 '" value="' . $perm . '" ' . $checked . $disabled . '/> ';
             if ($perm == CAP_INHERIT) {
                 $inherited = $this->parentpermissions[$capability->name];
@@ -95,8 +96,9 @@ class core_role_override_permissions_table_advanced extends core_role_capability
                 }
                 $strperm .= ' (' . $inherited . ')';
             }
-            echo '<span class="note">' . $strperm . '</span>';
-            echo '</label></td>';
+            $content .= '<span class="note">' . $strperm . '</span>';
+            $content .= '</label></td>';
         }
+        return $content;
     }
 }
index aa9a2f4..9f339d8 100644 (file)
@@ -67,6 +67,8 @@ class core_role_permissions_table extends core_role_capability_table_base {
 
     protected function add_row_cells($capability) {
         global $OUTPUT, $PAGE;
+        $renderer = $PAGE->get_renderer('core');
+        $adminurl = new moodle_url("/admin/");
 
         $context = $this->context;
         $contextid = $this->context->id;
@@ -75,7 +77,6 @@ class core_role_permissions_table extends core_role_capability_table_base {
         $overridableroles = $this->overridableroles;
         $roles = $this->roles;
 
-
         list($needed, $forbidden) = get_roles_with_cap_in_context($context, $capability->name);
         $neededroles    = array();
         $forbiddenroles = array();
@@ -91,40 +92,50 @@ class core_role_permissions_table extends core_role_capability_table_base {
 
         foreach ($roles as $id => $name) {
             if (isset($needed[$id])) {
-                $neededroles[$id] = $roles[$id];
+                $templatecontext = array("rolename" => $name, "roleid" => $id, "action" => "prevent", "spanclass" => "allowed",
+                                  "linkclass" => "preventlink", "adminurl" => $adminurl->out(), "imageurl" => "");
                 if (isset($overridableroles[$id]) and ($allowoverrides or ($allowsafeoverrides and is_safe_capability($capability)))) {
-                    $preventurl = new moodle_url($PAGE->url, array('contextid'=>$contextid, 'roleid'=>$id, 'capability'=>$capability->name, 'prevent'=>1));
-                    $neededroles[$id] .= $OUTPUT->action_icon($preventurl, new pix_icon('t/delete', get_string('prevent', 'core_role')));
+                    $templatecontext['imageurl'] = $renderer->pix_url('t/delete');
                 }
+                $neededroles[$id] = $renderer->render_from_template('core/permissionmanager_role', $templatecontext);
             }
         }
-        $neededroles = implode(', ', $neededroles);
+        $neededroles = implode(' ', $neededroles);
         foreach ($roles as $id => $name) {
             if (isset($forbidden[$id])  and ($allowoverrides or ($allowsafeoverrides and is_safe_capability($capability)))) {
-                $forbiddenroles[$id] = $roles[$id];
+                $templatecontext = array("rolename" => $name, "roleid" => $id, "action" => "unprohibit",
+                                "spanclass" => "forbidden", "linkclass" => "unprohibitlink", "adminurl" => $adminurl->out(),
+                                "imageurl" => "");
                 if (isset($overridableroles[$id]) and prohibit_is_removable($id, $context, $capability->name)) {
-                    $unprohibiturl = new moodle_url($PAGE->url, array('contextid'=>$contextid, 'roleid'=>$id, 'capability'=>$capability->name, 'unprohibit'=>1));
-                    $forbiddenroles[$id] .= $OUTPUT->action_icon($unprohibiturl, new pix_icon('t/delete', get_string('delete')));
+                    $templatecontext['imageurl'] = $renderer->pix_url('t/delete');
                 }
+                $forbiddenroles[$id] = $renderer->render_from_template('core/permissionmanager_role', $templatecontext);
             }
         }
-        $forbiddenroles = implode(', ', $forbiddenroles);
+        $forbiddenroles = implode(' ', $forbiddenroles);
 
         if ($allowable and ($allowoverrides or ($allowsafeoverrides and is_safe_capability($capability)))) {
-            $allowurl = new moodle_url($PAGE->url, array('contextid'=>$contextid, 'capability'=>$capability->name, 'allow'=>1));
-            $neededroles .= '<div class="allowmore">'.$OUTPUT->action_icon($allowurl, new pix_icon('t/add', get_string('allow', 'core_role'))).'</div>';
+            $allowurl = new moodle_url($PAGE->url, array('contextid' => $contextid,
+                                       'capability' => $capability->name, 'allow' => 1));
+            $allowicon = $OUTPUT->action_icon($allowurl, new pix_icon('t/add', get_string('allow', 'core_role')), null,
+                                            array('class' => 'allowlink', 'data-action' => 'allow'));
+            $neededroles .= html_writer::div($allowicon, 'allowmore');
         }
 
         if ($forbitable and ($allowoverrides or ($allowsafeoverrides and is_safe_capability($capability)))) {
-            $prohibiturl = new moodle_url($PAGE->url, array('contextid'=>$contextid, 'capability'=>$capability->name, 'prohibit'=>1));
-            $forbiddenroles .= '<div class="prohibitmore">'.$OUTPUT->action_icon($prohibiturl, new pix_icon('t/add', get_string('prohibit', 'core_role'))).'</div>';
+            $prohibiturl = new moodle_url($PAGE->url, array('contextid' => $contextid,
+                                          'capability' => $capability->name, 'prohibit' => 1));
+            $prohibiticon = $OUTPUT->action_icon($prohibiturl, new pix_icon('t/add', get_string('prohibit', 'core_role')), null,
+                                                array('class' => 'prohibitlink', 'data-action' => 'prohibit'));
+            $forbiddenroles .= html_writer::div($prohibiticon, 'prohibitmore');
         }
 
         $risks = $this->get_risks($capability);
 
-        echo '<td>' . $risks . '</td>';
-        echo '<td>' . $neededroles . '</td>';
-        echo '<td>' . $forbiddenroles . '</td>';
+        $contents = html_writer::tag('td', $risks, array('class' => 'risks'));
+        $contents .= html_writer::tag('td', $neededroles, array('class' => 'allowedroles'));
+        $contents .= html_writer::tag('td', $forbiddenroles, array('class' => 'forbiddenroles'));
+        return $contents;
     }
 
     protected function get_risks($capability) {
@@ -147,4 +158,18 @@ class core_role_permissions_table extends core_role_capability_table_base {
 
         return $return;
     }
+
+    /**
+     * Add additional attributes to row
+     *
+     * @param stdClass $capability capability that this table row relates to.
+     * @return array key value pairs of attribute names and values.
+     */
+    protected function get_row_attributes($capability) {
+        return array(
+                'data-id' => $capability->id,
+                'data-name' => $capability->name,
+                'data-humanname' => get_capability_string($capability->name),
+        );
+    }
 }
index a39ff0c..0859969 100644 (file)
@@ -129,7 +129,7 @@ class core_role_view_role_definition_table extends core_role_define_role_table_a
         } else {
             $default = "&#xa0;";
         }
-        echo '<td class="' . $permname . '">' . $this->strperms[$permname] . '<span class="note">' .
+        return '<td class="' . $permname . '">' . $this->strperms[$permname] . '<span class="note">' .
             $default . '</span></td>';
 
     }
index 841732d..c83f819 100644 (file)
@@ -195,6 +195,15 @@ if ($capability && ($allowoverrides || ($allowsafeoverrides && is_safe_capabilit
 echo $OUTPUT->header();
 echo $OUTPUT->heading($title);
 
+$adminurl = new moodle_url('/admin/');
+$arguments = array('contextid' => $contextid,
+                'contextname' => $contextname,
+                'adminurl' => $adminurl->out());
+$PAGE->requires->strings_for_js(
+                                array('roleprohibitinfo', 'roleprohibitheader', 'roleallowinfo', 'roleallowheader',
+                                    'confirmunassigntitle', 'confirmroleunprohibit', 'confirmroleprevent', 'confirmunassignyes',
+                                    'confirmunassignno'), 'core_role');
+$PAGE->requires->js_call_amd('core/permissionmanager', 'initialize', array($arguments));
 $table = new core_role_permissions_table($context, $contextname, $allowoverrides, $allowsafeoverrides, $overridableroles);
 echo $OUTPUT->box_start('generalbox capbox');
 // Print link to advanced override page.
index 6a15dd4..9853ea5 100644 (file)
@@ -351,7 +351,7 @@ Feature: Set up contextual data for tests
     And I follow "Course 1"
     And I navigate to "Grades" node in "Course administration"
     And I expand "Setup" node
-    And I follow "Categories and items"
+    And I follow "Gradebook setup"
     Then I should see "Test Grade Item 1"
     And I follow "Edit   Test Grade Item 1"
     And I expand all fieldsets
@@ -434,7 +434,7 @@ Feature: Set up contextual data for tests
     And I follow "Course 1"
     And I navigate to "Grades" node in "Course administration"
     And I expand "Setup" node
-    And I follow "Categories and items"
+    And I follow "Gradebook setup"
     Then I should see "Test Outcome Grade Item 1"
     And I follow "Edit   Test Outcome Grade Item 1"
     And the field "Outcome" matches value "Grade outcome 1"
index 71c54fe..8ad4536 100644 (file)
@@ -63,7 +63,7 @@ if ($translatorsubmitted) {
     $checkin = optional_param('savecheckin', false, PARAM_RAW);
 
     if ($checkin === false) {
-        $nexturl = $PAGE->url;
+        $nexturl = new moodle_url($PAGE->url, array('p' => $currentpage));
     } else {
         $nexturl = new moodle_url('/admin/tool/customlang/index.php', array('action'=>'checkin', 'lng' => $lng, 'sesskey'=>sesskey()));
     }
index c733dda..224e76b 100644 (file)
@@ -133,6 +133,7 @@ class tool_customlang_renderer extends plugin_renderer_base {
         $output .= html_writer::start_tag('div');
         $output .= html_writer::empty_tag('input', array('type'=>'hidden', 'name'=>'translatorsubmitted', 'value'=>1));
         $output .= html_writer::empty_tag('input', array('type'=>'hidden', 'name'=>'sesskey', 'value'=>sesskey()));
+        $output .= html_writer::empty_tag('input', array('type'=>'hidden', 'name'=>'p', 'value'=>$translator->currentpage));
         $save1   = html_writer::empty_tag('input', array('type'=>'submit', 'name'=>'savecontinue', 'value'=>get_string('savecontinue', 'tool_customlang')));
         $save2   = html_writer::empty_tag('input', array('type'=>'submit', 'name'=>'savecheckin', 'value'=>get_string('savecheckin', 'tool_customlang')));
         $output .= html_writer::tag('fieldset', $save1.$save2, array('class'=>'buttonsbar'));
index 8095ab2..4120104 100644 (file)
@@ -23,7 +23,7 @@
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
-$string['healthnoproblemsfound'] = 'There is no health problem found!';
+$string['healthnoproblemsfound'] = 'No health problems were found!';
 $string['healthproblemsdetected'] = 'Health problems detected!';
 $string['healthproblemsolution'] = 'Health problem solution';
 $string['healthreturntomain'] = 'Continue';
index cafd9b9..0ae7097 100644 (file)
@@ -300,29 +300,21 @@ class tool_installaddon_validator {
         $this->versionphp = array();
         $info = $this->parse_version_php($fullpath);
 
-        if ($this->assertions['plugintype'] === 'mod') {
-            $type = 'module';
-        } else {
-            $type = 'plugin';
+        if (isset($info['module->version'])) {
+            $this->add_message(self::ERROR, 'versionphpsyntax', '$module');
+            return false;
         }
 
-        if (!isset($info[$type.'->version'])) {
-            if ($type === 'module' and isset($info['plugin->version'])) {
-                // Expect the activity module using $plugin in version.php instead of $module.
-                $type = 'plugin';
-                $this->versionphp['version'] = $info[$type.'->version'];
-                $this->add_message(self::INFO, 'pluginversion', $this->versionphp['version']);
-            } else {
-                $this->add_message(self::ERROR, 'missingversion');
-                return false;
-            }
-        } else {
-            $this->versionphp['version'] = $info[$type.'->version'];
+        if (isset($info['plugin->version'])) {
+            $this->versionphp['version'] = $info['plugin->version'];
             $this->add_message(self::INFO, 'pluginversion', $this->versionphp['version']);
+        } else {
+            $this->add_message(self::ERROR, 'missingversion');
+            return false;
         }
 
-        if (isset($info[$type.'->requires'])) {
-            $this->versionphp['requires'] = $info[$type.'->requires'];
+        if (isset($info['plugin->requires'])) {
+            $this->versionphp['requires'] = $info['plugin->requires'];
             if ($this->versionphp['requires'] > $this->assertions['moodleversion']) {
                 $this->add_message(self::ERROR, 'requiresmoodle', $this->versionphp['requires']);
                 return false;
@@ -330,24 +322,27 @@ class tool_installaddon_validator {
             $this->add_message(self::INFO, 'requiresmoodle', $this->versionphp['requires']);
         }
 
-        if (isset($info[$type.'->component'])) {
-            $this->versionphp['component'] = $info[$type.'->component'];
-            list($reqtype, $reqname) = core_component::normalize_component($this->versionphp['component']);
-            if ($reqtype !== $this->assertions['plugintype']) {
-                $this->add_message(self::ERROR, 'componentmismatchtype', array(
-                    'expected' => $this->assertions['plugintype'],
-                    'found' => $reqtype));
-                return false;
-            }
-            if ($reqname !== $this->rootdir) {
-                $this->add_message(self::ERROR, 'componentmismatchname', $reqname);
-                return false;
-            }
-            $this->add_message(self::INFO, 'componentmatch', $this->versionphp['component']);
+        if (!isset($info['plugin->component'])) {
+            $this->add_message(self::ERROR, 'missingcomponent');
+            return false;
+        }
+
+        $this->versionphp['component'] = $info['plugin->component'];
+        list($reqtype, $reqname) = core_component::normalize_component($this->versionphp['component']);
+        if ($reqtype !== $this->assertions['plugintype']) {
+            $this->add_message(self::ERROR, 'componentmismatchtype', array(
+                'expected' => $this->assertions['plugintype'],
+                'found' => $reqtype));
+            return false;
+        }
+        if ($reqname !== $this->rootdir) {
+            $this->add_message(self::ERROR, 'componentmismatchname', $reqname);
+            return false;
         }
+        $this->add_message(self::INFO, 'componentmatch', $this->versionphp['component']);
 
-        if (isset($info[$type.'->maturity'])) {
-            $this->versionphp['maturity'] = $info[$type.'->maturity'];
+        if (isset($info['plugin->maturity'])) {
+            $this->versionphp['maturity'] = $info['plugin->maturity'];
             if ($this->versionphp['maturity'] === 'MATURITY_STABLE') {
                 $this->add_message(self::INFO, 'maturity', $this->versionphp['maturity']);
             } else {
@@ -355,8 +350,8 @@ class tool_installaddon_validator {
             }
         }
 
-        if (isset($info[$type.'->release'])) {
-            $this->versionphp['release'] = $info[$type.'->release'];
+        if (isset($info['plugin->release'])) {
+            $this->versionphp['release'] = $info['plugin->release'];
             $this->add_message(self::INFO, 'release', $this->versionphp['release']);
         }
 
index 6f1ad09..4f869ca 100644 (file)
@@ -71,6 +71,9 @@ $string['validationmsg_filestatus_info'] = 'Attempting to extract file {$a->file
 $string['validationmsg_foundlangfile'] = 'Found language file';
 $string['validationmsg_maturity'] = 'Declared maturity level';
 $string['validationmsg_maturity_help'] = 'The plugin can declare its maturity level. If the maintainer considers the plugin stable, the declared maturity level will read MATURITY_STABLE. All other maturity levels (such as alpha or beta) should be considered unstable and a warning is raised.';
+$string['validationmsg_missingcomponent'] = 'Plugin does not declare its component name';
+$string['validationmsg_missingcomponent_help'] = 'All plugins must provide their full component name via the `$plugin->component` declaration in the version.php file.';
+$string['validationmsg_missingcomponent_link'] = 'Development:version.php';
 $string['validationmsg_missingexpectedlangenfile'] = 'English language file name mismatch';
 $string['validationmsg_missingexpectedlangenfile_info'] = 'The given plugin type is missing the expected English language file {$a}.';
 $string['validationmsg_missinglangenfile'] = 'No English language file found';
@@ -91,6 +94,7 @@ $string['validationmsg_rootdirinvalid_help'] = 'The name of the root directory i
 $string['validationmsg_targetexists'] = 'Target location already exists';
 $string['validationmsg_targetexists_help'] = 'The directory that the plugin is to be installed to must not yet exist.';
 $string['validationmsg_unknowntype'] = 'Unknown plugin type';
+$string['validationmsg_versionphpsyntax'] = 'Unsupported syntax detected in version.php file';
 $string['validationmsglevel_debug'] = 'Debug';
 $string['validationmsglevel_error'] = 'Error';
 $string['validationmsglevel_info'] = 'OK';
diff --git a/admin/tool/installaddon/tests/fixtures/nocomponent/baz/lang/en/auth_baz.php b/admin/tool/installaddon/tests/fixtures/nocomponent/baz/lang/en/auth_baz.php
new file mode 100644 (file)
index 0000000..4422fa6
--- /dev/null
@@ -0,0 +1,3 @@
+<?php
+
+$string['pluginname'] = 'This is a plugin with $plugin->component missing in its version.php';
diff --git a/admin/tool/installaddon/tests/fixtures/nocomponent/baz/version.php b/admin/tool/installaddon/tests/fixtures/nocomponent/baz/version.php
new file mode 100644 (file)
index 0000000..f87367e
--- /dev/null
@@ -0,0 +1,5 @@
+<?php
+
+$plugin->version = 2015080600;
+$plugin->release = 'B.A.Z. Auth fake plugin';
+//$plugin->component is missing here so the validation must fail.
index ebad339..5b76ce2 100644 (file)
@@ -1,9 +1,7 @@
 <?php
 
-$module->version = 10; // Ignored, this should use $plugin
 $plugin->version = 2013031900;
 $plugin->component = 'local_foobar';
 $plugin->requires = 2013031200;
-$module->release = 'We are not an activity module!';
 $plugin->maturity = MATURITY_ALPHA;
 //$plugin->release = 'And this is commented';
diff --git a/admin/tool/installaddon/tests/fixtures/plugindir/legacymod/lang/en/legacymod.php b/admin/tool/installaddon/tests/fixtures/plugindir/legacymod/lang/en/legacymod.php
new file mode 100644 (file)
index 0000000..01e066e
--- /dev/null
@@ -0,0 +1,3 @@
+<?php
+
+$string['modulename'] = 'Legacy activity module with $module in version.php';
diff --git a/admin/tool/installaddon/tests/fixtures/plugindir/legacymod/version.php b/admin/tool/installaddon/tests/fixtures/plugindir/legacymod/version.php
new file mode 100644 (file)
index 0000000..3434317
--- /dev/null
@@ -0,0 +1,4 @@
+<?php
+
+// Support for the $module has been dropped in Moodle 3.0.
+$module->version = 2013031900;
diff --git a/admin/tool/installaddon/tests/fixtures/wronglang/bah/lang/en/bah.php b/admin/tool/installaddon/tests/fixtures/wronglang/bah/lang/en/bah.php
new file mode 100644 (file)
index 0000000..70668e8
--- /dev/null
@@ -0,0 +1,3 @@
+<?php
+
+$string['pluginname'] = 'This would be valid filename for module, not a block';
diff --git a/admin/tool/installaddon/tests/fixtures/wronglang/bah/version.php b/admin/tool/installaddon/tests/fixtures/wronglang/bah/version.php
new file mode 100644 (file)
index 0000000..a4fe771
--- /dev/null
@@ -0,0 +1,4 @@
+<?php
+
+$plugin->version = 2014122455;
+$plugin->component = 'block_bah';
index d0342f5..44eb306 100644 (file)
@@ -134,6 +134,28 @@ class tool_installaddon_validator_testcase extends basic_testcase {
         $this->assertFalse($validator->execute());
         $this->assertTrue($this->has_message($validator->get_messages(), $validator::ERROR, 'missingversionphp'));
 
+        $validator = testable_tool_installaddon_validator::instance($fixtures.'/plugindir', array(
+            'legacymod/' => true,
+            'legacymod/version.php' => true,
+            'legacymod/lang/' => true,
+            'legacymod/lang/en/' => true,
+            'legacymod/lang/en/legacymod.php' => true));
+        $validator->assert_plugin_type('mod');
+        $validator->assert_moodle_version(0);
+        $this->assertFalse($validator->execute());
+        $this->assertTrue($this->has_message($validator->get_messages(), $validator::ERROR, 'versionphpsyntax', '$module'));
+
+        $validator = testable_tool_installaddon_validator::instance($fixtures.'/nocomponent', array(
+            'baz/' => true,
+            'baz/version.php' => true,
+            'baz/lang/' => true,
+            'baz/lang/en/' => true,
+            'baz/lang/en/auth_baz.php' => true));
+        $validator->assert_plugin_type('auth');
+        $validator->assert_moodle_version(0);
+        $this->assertFalse($validator->execute());
+        $this->assertTrue($this->has_message($validator->get_messages(), $validator::ERROR, 'missingcomponent'));
+
         $validator = testable_tool_installaddon_validator::instance($fixtures.'/plugindir', array(
             'foobar/' => true,
             'foobar/version.php' => true,
@@ -205,7 +227,7 @@ class tool_installaddon_validator_testcase extends basic_testcase {
         $this->assertTrue($this->has_message($validator->get_messages(), $validator::WARNING, 'multiplelangenfiles'));
         $this->assertTrue(is_null($validator->get_language_file_name()));
 
-        $validator = testable_tool_installaddon_validator::instance($fixtures.'/nolang', array(
+        $validator = testable_tool_installaddon_validator::instance($fixtures.'/wronglang', array(
             'bah/' => true,
             'bah/version.php' => true,
             'bah/lang/' => true,
index 4c22571..00f0c61 100644 (file)
@@ -24,6 +24,6 @@
 defined('MOODLE_INTERNAL') || die();
 
 $plugin->component  = 'tool_installaddon';
-$plugin->version    = 2015051100;
+$plugin->version    = 2015080601;
 $plugin->requires   = 2015050500;
 $plugin->maturity   = MATURITY_STABLE;
index 65a752d..a2ccf4e 100644 (file)
@@ -30,7 +30,7 @@ $string['langimportdisabled'] = 'Language import feature has been disabled. You
 $string['langpackinstalled'] = 'Language pack \'{$a}\' was successfully installed';
 $string['langpackinstalledevent'] = 'Language pack installed';
 $string['langpackremoved'] = 'Language pack \'{$a}\' was uninstalled';
-$string['langpacknotremoved'] = 'An error has occurred, language pack \'{$a}\' is not completely uninstalled, please check file permissions';
+$string['langpacknotremoved'] = 'An error has occurred; language pack \'{$a}\' is not completely uninstalled. Please check file permissions.';
 $string['langpackremovedevent'] = 'Language pack uninstalled';
 $string['langpackupdateskipped'] = 'Update of \'{$a}\' language pack skipped';
 $string['langpackuptodate'] = 'Language pack \'{$a}\' is up-to-date';
@@ -45,6 +45,6 @@ $string['pluginname'] = 'Language packs';
 $string['purgestringcaches'] = 'Purge string caches';
 $string['remotelangnotavailable'] = 'Because Moodle cannot connect to download.moodle.org, it is not possible for language packs to be installed automatically. Please download the appropriate ZIP file(s) from <a href="https://download.moodle.org/langpack/">download.moodle.org/langpack</a>, copy them to your {$a} directory and unzip them manually.';
 $string['selectlangs'] = 'Select languages to unistall!';
-$string['uninstall'] = 'Uninstall selected language packs';
+$string['uninstall'] = 'Uninstall selected language pack(s)';
 $string['uninstallconfirm'] = 'You are about to completely uninstall these language packs: <strong>{$a}</strong>. Are you sure?';
 $string['updatelangs'] = 'Update all installed language packs';
index ff21c8e..757d46e 100644 (file)
@@ -38,7 +38,7 @@ Feature: Manage language packs
     And I set the field "Available language packs" to "English - Pirate (en_ar)"
     And I press "Install selected language pack(s)"
     When I set the field "Installed language packs" to "English - Pirate (en_ar)"
-    And I press "Uninstall selected language pack"
+    And I press "Uninstall selected language pack(s)"
     And I press "Continue"
     Then I should see "Language pack 'en_ar' was uninstalled"
     And the "Installed language packs" select box should not contain "English - Pirate (en_ar)"
@@ -52,7 +52,7 @@ Feature: Manage language packs
     Given I log in as "admin"
     And I navigate to "Language packs" node in "Site administration > Language"
     When I set the field "Installed language packs" to "English (en)"
-    And I press "Uninstall selected language pack"
+    And I press "Uninstall selected language pack(s)"
     Then I should see "English language pack can not be uninstalled"
     And I navigate to "Live logs" node in "Site administration > Reports"
     And I should not see "Language pack uninstalled"
index 6904d48..659ee2e 100644 (file)
@@ -149,6 +149,14 @@ if ($execute = $options['execute']) {
         mtrace("... used " . ($DB->perf_get_queries() - $predbqueries) . " dbqueries");
         mtrace("... used " . (microtime(true) - $pretime) . " seconds");
         mtrace("Task failed: " . $e->getMessage());
+        if ($CFG->debugdeveloper) {
+            if (!empty($e->debuginfo)) {
+                mtrace("Debug info:");
+                mtrace($e->debuginfo);
+            }
+            mtrace("Backtrace:");
+            mtrace(format_backtrace($e->getTrace(), true));
+        }
         \core\task\manager::scheduled_task_failed($task);
         get_mailer('close');
         exit(1);
index eaf3f6c..22733b2 100644 (file)
@@ -187,7 +187,7 @@ class auth_plugin_cas extends auth_plugin_ldap {
         }
 
         // If Moodle is configured to use a proxy, phpCAS needs some curl options set.
-        if (!empty($CFG->proxyhost) && !is_proxybypass($this->config->hostname)) {
+        if (!empty($CFG->proxyhost) && !is_proxybypass(phpCAS::getServerLoginURL())) {
             phpCAS::setExtraCurlOption(CURLOPT_PROXY, $CFG->proxyhost);
             if (!empty($CFG->proxyport)) {
                 phpCAS::setExtraCurlOption(CURLOPT_PROXYPORT, $CFG->proxyport);
index c9f93b2..f0ed9b2 100644 (file)
@@ -31,7 +31,9 @@ class block_messages extends block_base {
         global $USER, $CFG, $DB, $OUTPUT;
 
         if (!$CFG->messaging) {
+            $this->content = new stdClass;
             $this->content->text = '';
+            $this->content->footer = '';
             if ($this->page->user_is_editing()) {
                 $this->content->text = get_string('disabled', 'message');
             }
index 4cdcdf7..d56c330 100644 (file)
@@ -41,9 +41,9 @@ $string['newspolitics'] = 'News &amp; Politics';
 $string['numberofvideos'] = 'Number of videos';
 $string['peopleblogs'] = 'People &amp; Blogs';
 $string['petsanimals'] = 'Pets &amp; Animals';
-$string['pluginname'] = 'Youtube';
+$string['pluginname'] = 'YouTube';
 $string['requesterror'] = 'Data could not be obtained from the server. Contact your administrator if the problem persist.';
 $string['scienceandtech'] = 'Science &amp; Tech';
 $string['sports'] = 'Sports';
-$string['tag_youtube:addinstance'] = 'Add a new youtube block';
+$string['tag_youtube:addinstance'] = 'Add a new YouTube block';
 $string['travel'] = 'Travel &amp; Places';
index 66a4be8..ff9e88b 100644 (file)
@@ -95,106 +95,7 @@ class block_tags extends block_base {
 
         require_once($CFG->dirroot.'/tag/locallib.php');
 
-        if (empty($CFG->block_tags_showcoursetags) or !$CFG->block_tags_showcoursetags) {
-
-            $this->content->text = tag_print_cloud(null, $this->config->numberoftags, true);
-
-        } else {
-            // Start of show course tags section.
-            require_once($CFG->dirroot.'/tag/coursetagslib.php');
-
-            // Page awareness.
-            $tagtype = 'all';
-            if ($SCRIPT == '/my/index.php') {
-                $tagtype = 'my';
-            } else if (isset($this->page->course->id)) {
-                if ($this->page->course->id != SITEID) {
-                    $tagtype = 'course';
-                }
-            }
-
-            // DB hits to get groups of marked up tags (if available).
-            // TODO check whether time limited personal tags are required.
-            $content = '';
-            $moretags = new moodle_url('/tag/coursetags_more.php', array('show'=>$tagtype));
-            if ($tagtype == 'all') {
-                $tags = coursetag_get_tags(0, 0, $this->config->tagtype, $this->config->numberoftags);
-            } else if ($tagtype == 'course') {
-                $tags = coursetag_get_tags($this->page->course->id, 0, $this->config->tagtype, $this->config->numberoftags);
-                $moretags->param('courseid', $this->page->course->id);
-            } else if ($tagtype == 'my') {
-                $tags = coursetag_get_tags(0, $USER->id, $this->config->tagtype, $this->config->numberoftags);
-            }
-            $tagcloud = tag_print_cloud($tags, 150, true);
-            if (!$tagcloud) {
-                $tagcloud = get_string('notagsyet', 'block_tags');
-            }
-
-            // Prepare the divs that display the groups of tags.
-            $content = get_string($tagtype."tags", 'block_tags').
-                    '<div class="coursetag_list">'.$tagcloud.'</div>
-                    <div class="coursetag_morelink">
-                        <a href="'.$moretags->out().'" title="'.get_string('moretags', 'block_tags').'">'
-                        .get_string('more', 'block_tags').'</a>
-                    </div>';
-            // Add javascript.
-            coursetag_get_jscript();
-
-            // Add the divs (containing the tags) to the block's content.
-            $this->content->text .= $content;
-
-            // Add the input form section (allowing a user to tag the current course) and navigation, or login message.
-            if (isloggedin() && !isguestuser()) {
-                // Only show the input form on course pages for those allowed (or not barred).
-                if ($tagtype == 'course' &&
-                                has_capability('moodle/tag:create', context_course::instance($this->page->course->id))) {
-                    $buttonadd = get_string('add', 'block_tags');
-                    $arrowtitle = get_string('arrowtitle', 'block_tags');
-                    $edittags = get_string('edittags', 'block_tags');
-                    $sesskey = sesskey();
-                    $arrowright = $OUTPUT->pix_url('t/arrow_left');
-                    $redirect = $this->page->url->out();
-                    $this->content->footer .= <<<EOT
-                        <hr />
-                        <form action="{$CFG->wwwroot}/tag/coursetags_add.php" method="post" id="coursetag"
-                                onsubmit="return ctags_checkinput(this.coursetag_new_tag.value)">
-                            <div style="display: none;">
-                                <input type="hidden" name="entryid" value="$COURSE->id" />
-                                <input type="hidden" name="userid" value="$USER->id" />
-                                <input type="hidden" name="sesskey" value="$sesskey" />
-                                <input type="hidden" name="returnurl" value="$redirect" />
-                                </div>
-                            <div class="coursetag_form_wrapper">
-                                <div class="coursetag_form_positioner">
-                                    <div class="coursetag_form_input1">
-                                        <input type="text" name="coursetag_sug_keyword" class="coursetag_form_input1a" disabled="disabled" />
-                                    </div>
-                                    <div class="coursetag_form_input2">
-                                        <input type="text" name="coursetag_new_tag" id="coursetag_new_tag"
-                                        class="coursetag_form_input2a" onfocus="ctags_getKeywords()" onkeyup="ctags_getKeywords()" maxlength="50" />
-                                    </div>
-                                    <div class="coursetag_form_input3" id="coursetag_sug_btn">
-                                        <a title="$arrowtitle">
-                                            <img src="$arrowright" width="10" height="10" alt="enter" onclick="ctags_setKeywords()" />
-                                        </a>
-                                    </div>
-                                </div>
-                                <div style="display: inline;">
-                                    <button type="submit">$buttonadd</button>
-                                    <a href="$CFG->wwwroot/tag/coursetags_edit.php?courseid=$COURSE->id" title="$edittags">$edittags</a>
-                                </div>
-                            </div>
-                        </form>
-EOT;
-                }
-            } else {
-                // If not logged in.
-                $this->content->footer = '<hr />'.get_string('please', 'block_tags').'
-                    <a href="'.get_login_url().'">'.get_string('login', 'block_tags').'
-                        </a> '.get_string('tagunits', 'block_tags');
-            }
-        }
-        // End of show course tags section.
+        $this->content->text = tag_print_cloud(null, $this->config->numberoftags, true);
 
         return $this->content;
     }
diff --git a/blocks/tags/coursetags.js b/blocks/tags/coursetags.js
deleted file mode 100644 (file)
index 783146f..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-/**
- * coursetags.js
- * @author j.beedell@open.ac.uk July07
- *
- * getKeywords modified from an original script (Auto Complete Textfield)
- * from The JavaScript Source http://javascript.internet.com
- * originally created by: Timothy Groves http://www.brandspankingnew.net/
- */
-
-
-function ctags_show_div(mydiv) {
-    for(x in coursetagdivs) {
-        if(mydiv == coursetagdivs[x]) {
-            document.getElementById(coursetagdivs[x]).style.display="block";
-        } else {
-            document.getElementById(coursetagdivs[x]).style.display="none";
-        }
-    }
-    return false;
-}
-
-var sug = "";
-var sug_disp = "";
-
-function ctags_getKeywords() {
-  /*
-  // This 'workaround' removing the xhtml strict form autocomplete="off" needs to
-  // be added to the body onload() script to work - but decided not to include
-  // (having the browser list might help with screen readers more than this script)
-  // document.forms['coursetag'].setAttribute("autocomplete", "off");
-  */
-  var input = document.forms['coursetag'].coursetag_new_tag.value;
-  var len = input.length;
-  sug_disp = ""; sug = "";
-
-  if (input.length) {
-    for (ele in coursetag_tags)
-    {
-      if (coursetag_tags[ele].substr(0,len).toLowerCase() == input.toLowerCase())
-      {
-        sug_disp = input + coursetag_tags[ele].substr(len);
-        sug = coursetag_tags[ele];
-        break;
-      }
-    }
-  }
-  document.forms['coursetag'].coursetag_sug_keyword.value = sug_disp;
-  if (!sug.length || input == sug_disp) {
-    document.getElementById('coursetag_sug_btn').style.display = "none";
-  } else {
-    document.getElementById('coursetag_sug_btn').style.display = "block";
-  }
-}
-
-function ctags_setKeywords() {
-  document.forms['coursetag'].coursetag_new_tag.value = sug;
-  ctags_hideSug();
-}
-
-function ctags_hideSug() {
-  document.forms['coursetag'].coursetag_sug_keyword.value = "";
-  document.getElementById('coursetag_sug_btn').style.display = "none";
-}
index c63f492..edf9669 100644 (file)
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
+$string['configtitle'] = 'Block title';
+$string['disabledtags'] = 'Tags are disabled';
+$string['defaultdisplay'] = 'Tag type to display';
+$string['pluginname'] = 'Tags';
+$string['tags:addinstance'] = 'Add a new tags block';
+$string['tags:myaddinstance'] = 'Add a new tags block to Dashboard';
+
+// Deprecated since 3.0
+
 $string['add'] = 'Add';
 $string['alltags'] = 'All tags:';
 $string['arrowtitle'] = 'Click here to enter the suggested text (grey letters).';
-$string['configtitle'] = 'Block title';
 $string['coursetags'] = 'Course tags:';
-$string['disabledtags'] = 'Tags are disabled';
-$string['defaultdisplay'] = 'Tag type to display';
 $string['edit'] = 'edit...';
 $string['editdeletemytag'] = 'Delete tag from this course:';
 $string['editmytags'] = 'My tags - shortcuts to all your tagged courses.';
@@ -65,13 +71,10 @@ $string['mycoursetags'] = 'My course tags:';
 $string['mytags'] = 'My tags:';
 $string['notagsyet'] = 'No tags yet';
 $string['please'] = 'Please';
-$string['pluginname'] = 'Tags';
 $string['select'] = 'Select...';
 $string['showcoursetags'] = 'Show course tags';
 $string['showcoursetagsdef'] = 'Display the course tagging features in the tags block, allowing students to tag courses.';
 $string['suggestedtagthisunit'] = 'Suggested tag to this course:';
 $string['tags'] = 'tags';
-$string['tags:addinstance'] = 'Add a new tags block';
-$string['tags:myaddinstance'] = 'Add a new tags block to Dashboard';
 $string['tagthisunit'] = 'Tag this course:';
 $string['tagunits'] = 'to tag your favourite courses';
diff --git a/blocks/tags/lang/en/deprecated.txt b/blocks/tags/lang/en/deprecated.txt
new file mode 100644 (file)
index 0000000..7c83a9d
--- /dev/null
@@ -0,0 +1,42 @@
+add,block_tags
+alltags,block_tags
+arrowtitle,block_tags
+coursetags,block_tags
+edit,block_tags
+editdeletemytag,block_tags
+editmytags,block_tags
+editmytagsfor,block_tags
+editnopersonaltags,block_tags
+edittags,block_tags
+edittagthisunit,block_tags
+editthiscoursetags,block_tags
+edittitle,block_tags
+entries,block_tags
+entry,block_tags
+jserror1,block_tags
+jserror2,block_tags
+login,block_tags
+more,block_tags
+moreorder,block_tags
+moreorderalpha,block_tags
+moreorderdate,block_tags
+moreorderpop,block_tags
+moreshow,block_tags
+moreshowalltags,block_tags
+moreshowcommtags,block_tags
+moreshowcoursetags,block_tags
+moreshowmytags,block_tags
+moreshowofficialtags,block_tags
+moretags,block_tags
+moretitle,block_tags
+morewelcome,block_tags
+mytags,block_tags
+notagsyet,block_tags
+please,block_tags
+select,block_tags
+showcoursetags,block_tags
+showcoursetagsdef,block_tags
+suggestedtagthisunit,block_tags
+tags,block_tags
+tagthisunit,block_tags
+tagunits,block_tags
diff --git a/blocks/tags/styles.css b/blocks/tags/styles.css
deleted file mode 100644 (file)
index 59b1afc..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-.block_tags {}
-.block_tags #coursetag {}
-.block_tags #coursetag .coursetag_form_wrapper {}
-.block_tags #coursetag .coursetag_form_wrapper .coursetag_form_positioner {position: relative;}
-.block_tags #coursetag .coursetag_form_wrapper .coursetag_form_positioner .coursetag_form_input1 {position: relative;top: 0;left: 0;z-index: 1;width:100%;}
-.block_tags #coursetag .coursetag_form_wrapper .coursetag_form_positioner .coursetag_form_input2 {position: absolute;top: 0;left: 0;z-index: 2;width:100%;}
-.block_tags #coursetag .coursetag_form_wrapper .coursetag_form_positioner .coursetag_form_input3 {position: absolute;top: 3px;left: 12.8em;display: none;}
-.block_tags #coursetag .coursetag_form_wrapper .coursetag_form_positioner .coursetag_form_input1a {background-color: white; border: 1px solid #999;width: 12em;padding: 2px;}
-.block_tags #coursetag .coursetag_form_wrapper .coursetag_form_positioner .coursetag_form_input2a {background-color: transparent; border: 1px solid #999;width: 12em;color: #669954;padding: 2px;}
-.block_tags .coursetag_morelink {}
-.block_tags .coursetag_list {}
\ No newline at end of file
diff --git a/blocks/tags/tests/behat/coursetags.feature b/blocks/tags/tests/behat/coursetags.feature
deleted file mode 100644 (file)
index c5f9c55..0000000
+++ /dev/null
@@ -1,64 +0,0 @@
-@block @block_tags @core_tag
-Feature: Block tags displaying course tags
-  In order to tag courses
-  As a user
-  I need to be able to use the block tags
-
-  Background:
-    Given the following "users" exist:
-      | username | firstname | lastname | email |
-      | teacher1 | Teacher | 1 | teacher1@example.com |
-      | student1 | Student | 1 | student1@example.com |
-      | student2 | Student | 2 | student2@example.com |
-    And the following "courses" exist:
-      | fullname  | shortname |
-      | Course 1  | c1        |
-    And the following "tags" exist:
-      | name         | tagtype  |
-      | Neverusedtag | official |
-    And the following "course enrolments" exist:
-      | user     | course | role           |
-      | teacher1 | c1     | editingteacher |
-      | student1 | c1     | student        |
-      | student2 | c1     | student        |
-    And I log in as "admin"
-    And I set the following administration settings values:
-      | Show course tags | 1 |
-    And I log out
-
-  Scenario: Add Tags block to tag courses in a course
-    When I log in as "teacher1"
-    And I follow "Course 1"
-    And I turn editing mode on
-    And I add the "Tags" block
-    And I log out
-    And I log in as "student1"
-    And I follow "Course 1"
-    And I should not see "Neverusedtag" in the "Tags" "block"
-    And I click on "more..." "link" in the "Tags" "block"
-    And I should not see "Neverusedtag"
-    And I follow "c1"
-    And I set the field "coursetag_new_tag" to "Dogs, Mice"
-    And I press "Add"
-    And I should see "Dogs" in the "Tags" "block"
-    And I should see "Mice" in the "Tags" "block"
-    And I log out
-    And I log in as "student2"
-    And I follow "Course 1"
-    And I should see "Dogs" in the "Tags" "block"
-    And I set the field "coursetag_new_tag" to "Cats, Dogs"
-    And I press "Add"
-    And I should see "Dogs" in the "Tags" "block"
-    And I should see "Cats" in the "Tags" "block"
-    And I click on "more..." "link" in the "Tags" "block"
-    And "Cats" "link" should appear before "Dogs" "link"
-    And "Dogs" "link" should appear before "Mice" "link"
-    And I follow "My tags"
-    And I should see "Dogs"
-    And I should see "Cats"
-    And I should not see "Mice"
-    And I follow "All tags"
-    And I follow "Popularity"
-    And "Mice" "link" should appear before "Dogs" "link"
-    And I should not see "Neverusedtag"
-    And I log out
index 67146c0..95e1705 100644 (file)
@@ -4,17 +4,31 @@ Feature: Add and configure blocks throughout the site
   As a manager
   I need to set and configure blocks throughout the site
 
-  Scenario: Add and configure a block throughtout the site
+  Background:
     Given the following "courses" exist:
       | fullname | shortname | category |
       | Course 1 | C1 | 0 |
     And the following "users" exist:
       | username | firstname | lastname | email |
       | manager1 | Manager | 1 | manager1@example.com |
+      | teacher1 | teacher | 1 | teacher@example.com |
+    And the following "course enrolments" exist:
+      | user     | course | role           |
+      | teacher1 | C1     | editingteacher |
     And the following "system role assigns" exist:
       | user | course | role |
       | manager1 | Acceptance test site | manager |
-    And I log in as "manager1"
+    # Allow at least one role assignment in the block context:
+    And I log in as "admin"
+    And I navigate to "Define roles" node in "Site administration > Users > Permissions"
+    And I follow "Edit Non-editing teacher role"
+    And I set the following fields to these values:
+      | Block | 1 |
+    And I press "Save changes"
+    And I log out
+
+  Scenario: Add and configure a block throughtout the site
+    Given I log in as "manager1"
     And I am on site homepage
     And I follow "Turn editing on"
     And I add the "Comments" block
@@ -35,25 +49,13 @@ Feature: Add and configure blocks throughout the site
     And I should see "Comments" in the "//*[@id='region-pre' or @id='block-region-side-pre']/descendant::div[contains(concat(' ', normalize-space(@class), ' '), ' block ')]" "xpath_element"
 
   Scenario: Blocks on the dashboard page cannot have roles assigned to them
-    Given the following "users" exist:
-      | username | firstname | lastname | email |
-      | manager1 | Manager | 1 | manager1@example.com |
-    And I log in as "manager1"
+    Given I log in as "manager1"
     And I click on "Dashboard" "link" in the "Navigation" "block"
     When I press "Customise this page"
     Then I should not see "Assign roles in Navigation block"
 
   Scenario: Blocks on courses can have roles assigned to them
-    Given the following "courses" exist:
-      | fullname | shortname | category |
-      | Course 1 | C1 | 0 |
-    And the following "users" exist:
-      | username | firstname | lastname | email               |
-      | teacher1 | teacher   | 1        | teacher@example.com |
-    And the following "course enrolments" exist:
-      | user     | course | role           |
-      | teacher1 | C1     | editingteacher |
-    And I log in as "teacher1"
+    Given I log in as "teacher1"
     And I follow "Course 1"
     And I follow "Turn editing on"
     Then I should see "Assign roles in Search forums block"
index d464901..dc57b66 100644 (file)
@@ -365,7 +365,7 @@ class core_completion_external extends external_api {
                                          'type' => new external_value(PARAM_TEXT, 'Type description'),
                                          'criteria' => new external_value(PARAM_RAW, 'Criteria description'),
                                          'requirement' => new external_value(PARAM_TEXT, 'Requirement description'),
-                                         'status' => new external_value(PARAM_TEXT, 'Status description'),
+                                         'status' => new external_value(PARAM_RAW, 'Status description, can be anything'),
                                          ), 'details'),
                                  ), 'Completions'
                             ), ''
index 23e682c..f56e5dd 100644 (file)
@@ -15,7 +15,7 @@
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
 /**
- * Cron job for reviewing and aggregating course completion criteria
+ * Code used by scheduled tasks for reviewing and aggregating course completion criteria.
  *
  * @package core_completion
  * @category completion
 defined('MOODLE_INTERNAL') || die();
 require_once($CFG->libdir.'/completionlib.php');
 
-/**
- * Update user's course completion statuses
- *
- * First update all criteria completions, then aggregate all criteria completions
- * and update overall course completions
- */
-function completion_cron() {
-
-    completion_cron_mark_started();
-
-    completion_cron_criteria();
-
-    completion_cron_completions();
-}
-
 /**
  * Mark users as started if the config option is set
  *
index 6196a57..df033d9 100644 (file)
@@ -489,8 +489,8 @@ $CFG->admin = 'admin';
 //      $CFG->supportuserid = -20;
 //
 // Moodle 2.7 introduces a locking api for critical tasks (e.g. cron).
-// The default locking system to use is DB locking for MySQL and Postgres, and File
-// locking for Oracle and SQLServer. If $CFG->preventfilelocking is set, then the default
+// The default locking system to use is DB locking for Postgres, and file locking for
+// MySQL, Oracle and SQLServer. If $CFG->preventfilelocking is set, then the default
 // will always be DB locking. It can be manually set to one of the lock
 // factory classes listed below, or one of your own custom classes implementing the
 // \core\lock\lock_factory interface.
index 6f41d19..0189fae 100644 (file)
@@ -122,6 +122,12 @@ if (!empty($course)) {
         $course->{'role_'.$alias->roleid} = $alias->name;
     }
 
+    // Populate course tags.
+    if (!empty($CFG->usetags)) {
+        include_once($CFG->dirroot.'/tag/lib.php');
+        $course->tags = tag_get_tags_array('course', $course->id);
+    }
+
 } else {
     // Editor should respect category context if course context is not set.
     $editoroptions['context'] = $catcontext;
index 54359aa..3faea92 100644 (file)
@@ -301,6 +301,13 @@ class course_edit_form extends moodleform {
             }
         }
 
+        if (!empty($CFG->usetags) &&
+                ((empty($course->id) && guess_if_creator_will_have_course_capability('moodle/course:tag', $categorycontext))
+                || (!empty($course->id) && has_capability('moodle/course:tag', $coursecontext)))) {
+            $mform->addElement('header', 'tagshdr', get_string('tags', 'tag'));
+            $mform->addElement('tags', 'tags', get_string('tags'));
+        }
+
         // When two elements we need a group.
         $buttonarray = array();
         if ($returnto !== 0) {
index 7c89127..5cbc191 100644 (file)
@@ -2547,7 +2547,8 @@ function course_overviewfiles_options($course) {
  * @return object new course instance
  */
 function create_course($data, $editoroptions = NULL) {
-    global $DB;
+    global $DB, $CFG;
+    require_once($CFG->dirroot.'/tag/lib.php');
 
     //check the categoryid - must be given for all new courses
     $category = $DB->get_record('course_categories', array('id'=>$data->category), '*', MUST_EXIST);
@@ -2623,6 +2624,11 @@ function create_course($data, $editoroptions = NULL) {
     // set up enrolments
     enrol_course_updated(true, $course, $data);
 
+    // Update course tags.
+    if ($CFG->usetags && isset($data->tags)) {
+        tag_set('course', $course->id, $data->tags, 'core', context_course::instance($course->id)->id);
+    }
+
     // Trigger a course created event.
     $event = \core\event\course_created::create(array(
         'objectid' => $course->id,
@@ -2646,7 +2652,8 @@ function create_course($data, $editoroptions = NULL) {
  * @return void
  */
 function update_course($data, $editoroptions = NULL) {
-    global $DB;
+    global $DB, $CFG;
+    require_once($CFG->dirroot.'/tag/lib.php');
 
     $data->timemodified = time();
 
@@ -2733,6 +2740,11 @@ function update_course($data, $editoroptions = NULL) {
     // update enrol settings
     enrol_course_updated(false, $course, $data);
 
+    // Update course tags.
+    if ($CFG->usetags && isset($data->tags)) {
+        tag_set('course', $course->id, $data->tags, 'core', context_course::instance($course->id)->id);
+    }
+
     // Trigger a course updated event.
     $event = \core\event\course_updated::create(array(
         'objectid' => $course->id,
index 31d9391..ec26762 100644 (file)
@@ -1671,9 +1671,10 @@ class core_course_renderer extends plugin_renderer_base {
         $site = get_site();
         $output = '';
 
-        if (can_edit_in_category($category)) {
+        if (can_edit_in_category($coursecat->id)) {
             // Add 'Manage' button if user has permissions to edit this category.
-            $managebutton = $this->single_button(new moodle_url('/course/management.php'), get_string('managecourses'), 'get');
+            $managebutton = $this->single_button(new moodle_url('/course/management.php',
+                array('categoryid' => $coursecat->id)), get_string('managecourses'), 'get');
             $this->page->set_button($managebutton);
         }
         if (!$coursecat->id) {
diff --git a/course/tags.php b/course/tags.php
new file mode 100644 (file)
index 0000000..8e81fb1
--- /dev/null
@@ -0,0 +1,68 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Edit course tags
+ *
+ * @package    core_course
+ * @copyright  2015 Marina Glancy
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once("../config.php");
+require_once($CFG->dirroot . '/tag/lib.php');
+require_once($CFG->dirroot . '/course/tags_form.php');
+
+$id = required_param('id', PARAM_INT); // Course id.
+$returnurl = optional_param('return', null, PARAM_LOCALURL);
+$course = get_course($id);
+
+require_login();
+
+// Check capabilities but do not call require_login($course) - the user does not have to be enrolled.
+$context = context_course::instance($course->id);
+if (!$course->visible and !has_capability('moodle/course:viewhiddencourses', $context)) {
+    print_error('coursehidden', '', $CFG->wwwroot .'/');
+}
+require_capability('moodle/course:tag', $context);
+if (empty($CFG->usetags)) {
+    print_error('tagsaredisabled', 'tag');
+}
+
+$PAGE->set_course($course);
+$PAGE->set_pagelayout('incourse');
+$PAGE->set_url('/course/tags.php', array('id' => $course->id));
+$PAGE->set_title(get_string('coursetags', 'tag'));
+$PAGE->set_heading($course->fullname);
+
+$form = new coursetags_form();
+$data = array('id' => $course->id, 'tags' => tag_get_tags_array('course', $course->id));
+$form->set_data($data);
+
+$redirecturl = $returnurl ? new moodle_url($returnurl) : course_get_url($course);
+if ($form->is_cancelled()) {
+    redirect($redirecturl);
+} else if ($data = $form->get_data()) {
+    tag_set('course', $course->id, $data->tags, 'core', context_course::instance($course->id)->id);
+    redirect($redirecturl);
+}
+
+echo $OUTPUT->header();
+echo $OUTPUT->heading(get_string('coursetags', 'tag'));
+
+$form->display();
+
+echo $OUTPUT->footer();
diff --git a/course/tags_form.php b/course/tags_form.php
new file mode 100644 (file)
index 0000000..08cacc0
--- /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/>.
+
+/**
+ * Edit course tags form
+ *
+ * @package    core_course
+ * @copyright  2015 Marina Glancy
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->libdir.'/formslib.php');
+
+/**
+ * Edit course tags form
+ *
+ * @package    core_course
+ * @copyright  2015 Marina Glancy
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class coursetags_form extends moodleform {
+
+    /**
+     * Form definition
+     */
+    public function definition() {
+        $mform    = $this->_form;
+
+        $mform->addElement('tags', 'tags', get_string('tags'));
+
+        $mform->addElement('hidden', 'id', null);
+        $mform->setType('id', PARAM_INT);
+
+        $this->add_action_buttons();
+
+    }
+}
diff --git a/course/tests/behat/coursetags.feature b/course/tests/behat/coursetags.feature
new file mode 100644 (file)
index 0000000..425efa0
--- /dev/null
@@ -0,0 +1,98 @@
+@core @core_course @core_tag
+Feature: Tagging courses
+  In order to search courses
+  As a teacher
+  I need to be able to tag courses
+
+  Background:
+    Given the following "users" exist:
+      | username | firstname | lastname | email |
+      | teacher1 | Teacher | 1 | teacher1@example.com |
+      | teacher2 | Teacher | 2 | teacher2@example.com |
+      | user1    | User    | 1 | user1@example.com |
+    And the following "courses" exist:
+      | fullname  | shortname |
+      | Course 1  | c1        |
+      | Course 2  | c2        |
+    And the following "tags" exist:
+      | name         | tagtype  |
+      | Neverusedtag | official |
+    And the following "course enrolments" exist:
+      | user     | course | role           |
+      | teacher1 | c1     | editingteacher |
+      | teacher2 | c1     | teacher        |
+      | teacher1 | c2     | editingteacher |
+      | teacher2 | c2     | teacher        |
+    And I log in as "teacher1"
+    And I follow "Course 1"
+    And I click on "Edit settings" "link" in the "Administration" "block"
+    And I set the following fields to these values:
+      | Other tags (enter tags separated by commas) | Mathematics |
+    And I press "Save and display"
+    And I log out
+
+  Scenario: Set course tags using the course edit form
+    When I log in as "teacher1"
+    And I follow "Course 1"
+    And "Course tags" "link" should not exist in the "Administration" "block"
+    And I click on "Edit settings" "link" in the "Administration" "block"
+    And the field "Other tags (enter tags separated by commas)" matches value "Mathematics"
+    And I set the following fields to these values:
+      | Other tags (enter tags separated by commas) | Mathematics, Algebra |
+    And I press "Save and display"
+    And I click on "Dashboard" "link" in the "Navigation" "block"
+    And I follow "Course 2"
+    And I click on "Edit settings" "link" in the "Administration" "block"
+    And I set the following fields to these values:
+      | Other tags (enter tags separated by commas) | Mathematics, Geometry |
+    And I press "Save and display"
+    And I log out
+    And I log in as "user1"
+    And I navigate to "Tags" node in "Site pages"
+    And I follow "Mathematics"
+    Then I should see "Course 1"
+    And I should see "Course 2"
+    And I follow "Tags"
+    And I follow "Algebra"
+    And I should see "Course 1"
+    And I should not see "Course 2"
+    And I follow "Tags"
+    And I follow "Geometry"
+    And I should not see "Course 1"
+    And I should see "Course 2"
+    And I log out
+
+  Scenario: User can set course tags using separate form
+    Given I log in as "admin"
+    And I set the following system permissions of "Non-editing teacher" role:
+      | moodle/course:tag | Allow |
+    And I log out
+    When I log in as "teacher2"
+    And I follow "Course 1"
+    And "Edit settings" "link" should not exist in the "Administration" "block"
+    And I click on "Course tags" "link" in the "Administration" "block"
+    And the field "Other tags (enter tags separated by commas)" matches value "Mathematics"
+    And I set the following fields to these values:
+      | Other tags (enter tags separated by commas) | Mathematics, Algebra |
+    And I press "Save changes"
+    And I click on "Dashboard" "link" in the "Navigation" "block"
+    And I follow "Course 2"
+    And I click on "Course tags" "link" in the "Administration" "block"
+    And I set the following fields to these values:
+      | Other tags (enter tags separated by commas) | Mathematics, Geometry |
+    And I press "Save changes"
+    And I log out
+    And I log in as "user1"
+    And I navigate to "Tags" node in "Site pages"
+    And I follow "Mathematics"
+    Then I should see "Course 1"
+    And I should see "Course 2"
+    And I follow "Tags"
+    And I follow "Algebra"
+    And I should see "Course 1"
+    And I should not see "Course 2"
+    And I follow "Tags"
+    And I follow "Geometry"
+    And I should not see "Course 1"
+    And I should see "Course 2"
+    And I log out
index c8e086a..6579845 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 1704f91..3da12bd 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 c8e086a..6579845 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 c8683e3..c4af5f4 100644 (file)
@@ -293,7 +293,7 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
             };
             this.send_request(data);
             if (M.core.actionmenu && M.core.actionmenu.instance) {
-                M.core.actionmenu.instance.hideMenu();
+                M.core.actionmenu.instance.hideMenu(ev);
             }
 
         }, this);
@@ -540,7 +540,7 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
 
         this.send_request(data, null, function(response) {
             if (M.core.actionmenu && M.core.actionmenu.instance) {
-                M.core.actionmenu.instance.hideMenu();
+                M.core.actionmenu.instance.hideMenu(ev);
             }
 
             // Try to retrieve the existing string from the server
index 7aab283..dd62786 100644 (file)
@@ -192,9 +192,6 @@ class enrol_meta_observer extends enrol_meta_handler {
         }
 
         foreach ($enrols as $enrol) {
-            $enrol->customint = 0;
-            $DB->update_record('enrol', $enrol);
-
             if ($unenrolaction == ENROL_EXT_REMOVED_SUSPEND or $unenrolaction == ENROL_EXT_REMOVED_SUSPENDNOROLES) {
                 // This makes all enrolments suspended very quickly.
                 $plugin->update_status($enrol, ENROL_INSTANCE_DISABLED);
index 08c50c8..a7af91c 100644 (file)
@@ -95,7 +95,7 @@ class enrol_meta_handler {
         list($enabled, $params) = $DB->get_in_or_equal(explode(',', $CFG->enrol_plugins_enabled), SQL_PARAMS_NAMED, 'e');
         $params['userid'] = $userid;
         $params['parentcourse'] = $instance->customint1;
-        $sql = "SELECT ue.*
+        $sql = "SELECT ue.*, e.status AS enrolstatus
                   FROM {user_enrolments} ue
                   JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol <> 'meta' AND e.courseid = :parentcourse AND e.enrol $enabled)
                  WHERE ue.userid = :userid";
@@ -142,23 +142,33 @@ class enrol_meta_handler {
             return;
         }
 
-        // is parent enrol active? (we ignore enrol starts and ends, sorry it would be too complex)
+        // Is parent enrol active? Find minimum timestart and maximum timeend of all active enrolments.
         $parentstatus = ENROL_USER_SUSPENDED;
+        $parenttimeend = null;
+        $parenttimestart = null;
         foreach ($parentues as $pue) {
-            if ($pue->status == ENROL_USER_ACTIVE) {
+            if ($pue->status == ENROL_USER_ACTIVE && $pue->enrolstatus == ENROL_INSTANCE_ENABLED) {
                 $parentstatus = ENROL_USER_ACTIVE;
-                break;
+                if ($parenttimeend === null || $pue->timeend == 0 || ($parenttimeend && $parenttimeend < $pue->timeend)) {
+                    $parenttimeend = $pue->timeend;
+                }
+                if ($parenttimestart === null || $parenttimestart > $pue->timestart) {
+                    $parenttimestart = $pue->timestart;
+                }
             }
         }
 
-        // enrol user if not enrolled yet or fix status
+        // Enrol user if not enrolled yet or fix status/timestart/timeend. Use the minimum timestart and maximum timeend found above.
         if ($ue) {
-            if ($parentstatus != $ue->status) {
-                $plugin->update_user_enrol($instance, $userid, $parentstatus);
+            if ($parentstatus != $ue->status ||
+                    ($parentstatus == ENROL_USER_ACTIVE && ($parenttimestart != $ue->timestart || $parenttimeend != $ue->timeend))) {
+                $plugin->update_user_enrol($instance, $userid, $parentstatus, $parenttimestart, $parenttimeend);
                 $ue->status = $parentstatus;
+                $ue->timestart = $parenttimestart;
+                $ue->timeend = $parenttimeend;
             }
         } else {
-            $plugin->enrol_user($instance, $userid, NULL, 0, 0, $parentstatus);
+            $plugin->enrol_user($instance, $userid, NULL, (int)$parenttimestart, (int)$parenttimeend, $parentstatus);
             $ue = new stdClass();
             $ue->userid = $userid;
             $ue->enrolid = $instance->id;
@@ -170,11 +180,13 @@ class enrol_meta_handler {
 
         $unenrolaction = $plugin->get_config('unenrolaction', ENROL_EXT_REMOVED_SUSPENDNOROLES);
 
-        // only active users in enabled instances are supposed to have roles (we can reassign the roles any time later)
-        if ($ue->status != ENROL_USER_ACTIVE or $instance->status != ENROL_INSTANCE_ENABLED) {
+        // Only active users in enabled instances are supposed to have roles (we can reassign the roles any time later).
+        if ($ue->status != ENROL_USER_ACTIVE or $instance->status != ENROL_INSTANCE_ENABLED or
+                ($parenttimeend and $parenttimeend < time()) or ($parenttimestart > time())) {
             if ($unenrolaction == ENROL_EXT_REMOVED_SUSPEND) {
                 // Always keep the roles.
             } else if ($roles) {
+                // This will only unassign roles that were assigned in this enrolment method, leaving all manual role assignments intact.
                 role_unassign_all(array('userid'=>$userid, 'contextid'=>$context->id, 'component'=>'enrol_meta', 'itemid'=>$instance->id));
             }
             return;
@@ -279,17 +291,27 @@ function enrol_meta_sync($courseid = NULL, $verbose = false) {
     $allroles = get_all_roles();
 
 
-    // iterate through all not enrolled yet users
+    // Iterate through all not enrolled yet users. For each active enrolment of each user find the minimum
+    // enrolment startdate and maximum enrolment enddate.
+    // This SQL relies on the fact that ENROL_USER_ACTIVE < ENROL_USER_SUSPENDED
+    // and ENROL_INSTANCE_ENABLED < ENROL_INSTANCE_DISABLED. Condition "pue.status + pe.status = 0" means
+    // that enrolment is active. When MIN(pue.status + pe.status)=0 it means there exists an active
+    // enrolment.
     $onecourse = $courseid ? "AND e.courseid = :courseid" : "";
     list($enabled, $params) = $DB->get_in_or_equal(explode(',', $CFG->enrol_plugins_enabled), SQL_PARAMS_NAMED, 'e');
     $params['courseid'] = $courseid;
-    $sql = "SELECT pue.userid, e.id AS enrolid, pue.status
+    $sql = "SELECT pue.userid, e.id AS enrolid, MIN(pue.status + pe.status) AS status,
+                      MIN(CASE WHEN (pue.status + pe.status = 0) THEN pue.timestart ELSE 9999999999 END) AS timestart,
+                      MAX(CASE WHEN (pue.status + pe.status = 0) THEN
+                                (CASE WHEN pue.timeend = 0 THEN 9999999999 ELSE pue.timeend END)
+                                ELSE 0 END) AS timeend
               FROM {user_enrolments} pue
               JOIN {enrol} pe ON (pe.id = pue.enrolid AND pe.enrol <> 'meta' AND pe.enrol $enabled)
               JOIN {enrol} e ON (e.customint1 = pe.courseid AND e.enrol = 'meta' $onecourse)
               JOIN {user} u ON (u.id = pue.userid AND u.deleted = 0)
          LEFT JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = pue.userid)
-             WHERE ue.id IS NULL";
+             WHERE ue.id IS NULL
+             GROUP BY pue.userid, e.id";
 
     $rs = $DB->get_recordset_sql($sql, $params);
     foreach($rs as $ue) {
@@ -314,7 +336,15 @@ function enrol_meta_sync($courseid = NULL, $verbose = false) {
             }
         }
 
-        $meta->enrol_user($instance, $ue->userid, $ue->status);
+        // So now we have aggregated values that we will use for the meta enrolment status, timeend and timestart.
+        // Again, we use the fact that active=0 and disabled/suspended=1. Only when MIN(pue.status + pe.status)=0 the enrolment is active:
+        $ue->status = ($ue->status == ENROL_USER_ACTIVE + ENROL_INSTANCE_ENABLED) ? ENROL_USER_ACTIVE : ENROL_USER_SUSPENDED;
+        // Timeend 9999999999 was used instead of 0 in the "MAX()" function:
+        $ue->timeend = ($ue->timeend == 9999999999) ? 0 : (int)$ue->timeend;
+        // Timestart 9999999999 is only possible when there are no active enrolments:
+        $ue->timestart = ($ue->timestart == 9999999999) ? 0 : (int)$ue->timestart;
+
+        $meta->enrol_user($instance, $ue->userid, null, $ue->timestart, $ue->timeend, $ue->status);
         if ($instance->customint2) {
             groups_add_member($instance->customint2, $ue->userid, 'enrol_meta', $instance->id);
         }
@@ -371,29 +401,38 @@ function enrol_meta_sync($courseid = NULL, $verbose = false) {
     $rs->close();
 
 
-    // update status - meta enrols + start and end dates are ignored, sorry
-    // note the trick here is that the active enrolment and instance constants have value 0
+    // Update status - meta enrols are ignored to avoid recursion.
+    // Note the trick here is that the active enrolment and instance constants have value 0.
     $onecourse = $courseid ? "AND e.courseid = :courseid" : "";
     list($enabled, $params) = $DB->get_in_or_equal(explode(',', $CFG->enrol_plugins_enabled), SQL_PARAMS_NAMED, 'e');
     $params['courseid'] = $courseid;
-    $sql = "SELECT ue.userid, ue.enrolid, pue.pstatus
+    $sql = "SELECT ue.userid, ue.enrolid, pue.pstatus, pue.ptimestart, pue.ptimeend
               FROM {user_enrolments} ue
               JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = 'meta' $onecourse)
-              JOIN (SELECT xpue.userid, xpe.courseid, MIN(xpue.status + xpe.status) AS pstatus
+              JOIN (SELECT xpue.userid, xpe.courseid, MIN(xpue.status + xpe.status) AS pstatus,
+                      MIN(CASE WHEN (xpue.status + xpe.status = 0) THEN xpue.timestart ELSE 9999999999 END) AS ptimestart,
+                      MAX(CASE WHEN (xpue.status + xpe.status = 0) THEN
+                                (CASE WHEN xpue.timeend = 0 THEN 9999999999 ELSE xpue.timeend END)
+                                ELSE 0 END) AS ptimeend
                       FROM {user_enrolments} xpue
                       JOIN {enrol} xpe ON (xpe.id = xpue.enrolid AND xpe.enrol <> 'meta' AND xpe.enrol $enabled)
                   GROUP BY xpue.userid, xpe.courseid
                    ) pue ON (pue.courseid = e.customint1 AND pue.userid = ue.userid)
-             WHERE (pue.pstatus = 0 AND ue.status > 0) OR (pue.pstatus > 0 and ue.status = 0)";
+             WHERE (pue.pstatus = 0 AND ue.status > 0) OR (pue.pstatus > 0 and ue.status = 0)
+             OR ((CASE WHEN pue.ptimestart = 9999999999 THEN 0 ELSE pue.ptimestart END) <> ue.timestart)
+             OR ((CASE WHEN pue.ptimeend = 9999999999 THEN 0 ELSE pue.ptimeend END) <> ue.timeend)";
     $rs = $DB->get_recordset_sql($sql, $params);
     foreach($rs as $ue) {
         if (!isset($instances[$ue->enrolid])) {
             $instances[$ue->enrolid] = $DB->get_record('enrol', array('id'=>$ue->enrolid));
         }
         $instance = $instances[$ue->enrolid];
-        $ue->pstatus = ($ue->pstatus == ENROL_USER_ACTIVE) ? ENROL_USER_ACTIVE : ENROL_USER_SUSPENDED;
+        $ue->pstatus = ($ue->pstatus == ENROL_USER_ACTIVE + ENROL_INSTANCE_ENABLED) ? ENROL_USER_ACTIVE : ENROL_USER_SUSPENDED;
+        $ue->ptimeend = ($ue->ptimeend == 9999999999) ? 0 : (int)$ue->ptimeend;
+        $ue->ptimestart = ($ue->ptimestart == 9999999999) ? 0 : (int)$ue->ptimestart;
 
-        if ($ue->pstatus == ENROL_USER_ACTIVE and !$syncall and $unenrolaction != ENROL_EXT_REMOVED_UNENROL) {
+        if ($ue->pstatus == ENROL_USER_ACTIVE and (!$ue->ptimeend || $ue->ptimeend > time())
+                and !$syncall and $unenrolaction != ENROL_EXT_REMOVED_UNENROL) {
             // this may be slow if very many users are ignored in sync
             $parentcontext = context_course::instance($instance->customint1);
             list($ignoreroles, $params) = $DB->get_in_or_equal($skiproles, SQL_PARAMS_NAMED, 'ri', false, -1);
@@ -409,7 +448,7 @@ function enrol_meta_sync($courseid = NULL, $verbose = false) {
             }
         }
 
-        $meta->update_user_enrol($instance, $ue->userid, $ue->pstatus);
+        $meta->update_user_enrol($instance, $ue->userid, $ue->pstatus, $ue->ptimestart, $ue->ptimeend);
         if ($verbose) {
             if ($ue->pstatus == ENROL_USER_ACTIVE) {
                 mtrace("  unsuspending: $ue->userid ==> $instance->courseid");
index 772c8d0..4d3b549 100644 (file)
@@ -743,4 +743,117 @@ class enrol_meta_plugin_testcase extends advanced_testcase {
         // Check that the group name has been changed.
         $this->assertEquals('Physics course (3)', $groupinfo->name);
     }
+
+    /**
+     * Test that enrolment timestart-timeend is respected in meta course.
+     */
+    public function test_timeend() {
+        global $CFG, $DB;
+
+        $this->resetAfterTest(true);
+
+        $timeinfuture = time() + DAYSECS;
+        $timeinpast = time() - DAYSECS;
+
+        $metalplugin = enrol_get_plugin('meta');
+        $manplugin = enrol_get_plugin('manual');
+
+        $user1 = $this->getDataGenerator()->create_user();
+        $user2 = $this->getDataGenerator()->create_user();
+        $user3 = $this->getDataGenerator()->create_user();
+        $user4 = $this->getDataGenerator()->create_user();
+        $user5 = $this->getDataGenerator()->create_user();
+
+        $course1 = $this->getDataGenerator()->create_course();
+        $course2 = $this->getDataGenerator()->create_course();
+        $course3 = $this->getDataGenerator()->create_course();
+        $manual1 = $DB->get_record('enrol', array('courseid' => $course1->id, 'enrol' => 'manual'), '*', MUST_EXIST);
+
+        $student = $DB->get_record('role', array('shortname' => 'student'));
+
+        $this->enable_plugin();
+
+        // Create instance of enrol_meta in course2 when there are no enrolments present.
+        $meta2id = $metalplugin->add_instance($course2, array('customint1' => $course1->id));
+
+        $expectedenrolments = array(
+            $user1->id => array(0, 0, ENROL_USER_ACTIVE),
+            $user2->id => array($timeinpast, 0, ENROL_USER_ACTIVE),
+            $user3->id => array(0, $timeinfuture, ENROL_USER_ACTIVE),
+            $user4->id => array($timeinpast, $timeinfuture, ENROL_USER_ACTIVE),
+            $user5->id => array(0, 0, ENROL_USER_SUSPENDED),
+        );
+        foreach ($expectedenrolments as $userid => $data) {
+            $expectedenrolments[$userid] = (object)(array('userid' => $userid) +
+                    array_combine(array('timestart', 'timeend', 'status'), $data));
+        }
+
+        // Enrol users manually in course 1.
+        foreach ($expectedenrolments as $e) {
+            $manplugin->enrol_user($manual1, $e->userid, $student->id, $e->timestart, $e->timeend, $e->status);
+        }
+
+        $enrolments = $DB->get_records('user_enrolments', array('enrolid' => $manual1->id), 'userid', 'userid, timestart, timeend, status');
+        $this->assertEquals($expectedenrolments, $enrolments);
+
+        // Make sure that the same enrolments are now present in course2 under meta enrolment.
+        $enrolments = $DB->get_records('user_enrolments', array('enrolid' => $meta2id), '', 'userid, timestart, timeend, status');
+        $this->assertEquals($expectedenrolments, $enrolments);
+
+        // Create instance of enrol_meta in course3 and run sync.
+        $meta3id = $metalplugin->add_instance($course3, array('customint1' => $course1->id));
+        enrol_meta_sync($course3->id);
+
+        // Make sure that the same enrolments are now present in course3 under meta enrolment.
+        $enrolments = $DB->get_records('user_enrolments', array('enrolid' => $meta3id), '', 'userid, timestart, timeend, status');
+        $this->assertEquals($expectedenrolments, $enrolments);
+
+        // Update some of the manual enrolments.
+        $expectedenrolments[$user2->id]->timestart = $timeinpast - 60;
+        $expectedenrolments[$user3->id]->timeend = $timeinfuture + 60;
+        $expectedenrolments[$user4->id]->status = ENROL_USER_SUSPENDED;
+        $expectedenrolments[$user5->id]->status = ENROL_USER_ACTIVE;
+        foreach ($expectedenrolments as $e) {
+            $manplugin->update_user_enrol($manual1, $e->userid, $e->status, $e->timestart, $e->timeend);
+        }
+
+        // Make sure meta courses are also updated.
+        $enrolments = $DB->get_records('user_enrolments', array('enrolid' => $meta2id), '', 'userid, timestart, timeend, status');
+        $this->assertEquals($expectedenrolments, $enrolments);
+        $enrolments = $DB->get_records('user_enrolments', array('enrolid' => $meta3id), '', 'userid, timestart, timeend, status');
+        $this->assertEquals($expectedenrolments, $enrolments);
+
+        // Test meta sync. Imagine events are not working.
+        $sink = $this->redirectEvents();
+        $expectedenrolments[$user2->id]->timestart = $timeinpast;
+        $expectedenrolments[$user3->id]->timeend = $timeinfuture;
+        $expectedenrolments[$user4->id]->status = ENROL_USER_ACTIVE;
+        $expectedenrolments[$user5->id]->status = ENROL_USER_SUSPENDED;
+        foreach ($expectedenrolments as $e) {
+            $manplugin->update_user_enrol($manual1, $e->userid, $e->status, $e->timestart, $e->timeend);
+        }
+
+        // Make sure meta courses are updated only for the course that was synced.
+        enrol_meta_sync($course3->id);
+
+        $enrolments = $DB->get_records('user_enrolments', array('enrolid' => $meta2id), '', 'userid, timestart, timeend, status');
+        $this->assertNotEquals($expectedenrolments, $enrolments);
+
+        $enrolments = $DB->get_records('user_enrolments', array('enrolid' => $meta3id), '', 'userid, timestart, timeend, status');
+        $this->assertEquals($expectedenrolments, $enrolments);
+
+        $sink->close();
+
+        // Disable manual enrolment in course1 and make sure all user enrolments in course2 are suspended.
+        $manplugin->update_status($manual1, ENROL_INSTANCE_DISABLED);
+        $allsuspendedenrolemnts = array_combine(array_keys($expectedenrolments), array_fill(0, 5, ENROL_USER_SUSPENDED));
+        enrol_meta_sync($course3->id);
+        $enrolmentstatuses = $DB->get_records_menu('user_enrolments', array('enrolid' => $meta3id), '', 'userid, status');
+        $this->assertEquals($allsuspendedenrolemnts, $enrolmentstatuses);
+
+        $manplugin->update_status($manual1, ENROL_INSTANCE_ENABLED);
+        enrol_meta_sync($course3->id);
+        $enrolments = $DB->get_records('user_enrolments', array('enrolid' => $meta3id), '', 'userid, timestart, timeend, status');
+        $this->assertEquals($expectedenrolments, $enrolments);
+    }
 }
diff --git a/files/tests/behat/add_custom_file_type.feature b/files/tests/behat/add_custom_file_type.feature
new file mode 100644 (file)
index 0000000..03e2e38
--- /dev/null
@@ -0,0 +1,41 @@
+@core @core_files @_file_upload
+Feature: Add a new custom file type
+  In order to add files of a custom type
+  As an admin
+  I need to add a new custom file type
+
+
+  @javascript
+  Scenario: Add custom file type
+    Given the following "courses" exist:
+      | fullname | shortname | category | legacyfiles |
+      | Course 1 | C1 | 0 | 2 |
+    And the following "users" exist:
+      | username | firstname | lastname | email |
+      | teacher1 | Teacher | 1 | teacher1@example.com |
+    And the following "course enrolments" exist:
+      | user | course | role |
+      | teacher1 | C1 | editingteacher |
+    And I log in as "admin"
+    And I navigate to "File types" node in "Site administration>Server"
+    And I press "Add a new file type"
+    And I set the following fields to these values:
+      | Extension | mdlr |
+      | MIME type | application/x-moodle-rules |
+      | File icon | document                       |
+      | Description type | Custom description specified in this form |
+      | Custom description | Moodle rules |
+    And I press "Save changes"
+    And I should see "application/x-moodle-rules"
+    And I log out
+    And I log in as "teacher1"
+    And I follow "Course 1"
+    And I turn editing mode on
+    When I add a "File" to section "1" and I fill the form with:
+      | Name | Test file |
+      | Select files | files/tests/fixtures/custom_filetype.mdlr |
+      | Show type    | 1                             |
+      | Display resource description | 1             |
+    And I follow "Course 1"
+    Then I should see "Test file"
+    And I should see "Moodle rules" in the "span.resourcelinkdetails" "css_element"
diff --git a/files/tests/fixtures/custom_filetype.mdlr b/files/tests/fixtures/custom_filetype.mdlr
new file mode 100644 (file)
index 0000000..eaa394d
--- /dev/null
@@ -0,0 +1 @@
+empty file for testing purposes
\ No newline at end of file
index e73bc93..672888a 100644 (file)
@@ -15,7 +15,7 @@
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
 /**
- * Edit and review page for grade categories and items
+ * The Gradebook setup page.
  *
  * @package   core_grades
  * @copyright 2008 Nicolas Connault
@@ -244,7 +244,7 @@ if ($data = data_submitted() and confirm_sesskey()) {
     }
 }
 
-print_grade_page_head($courseid, 'settings', 'setup', get_string('categoriesanditems', 'grades'));
+print_grade_page_head($courseid, 'settings', 'setup', get_string('gradebooksetup', 'grades'));
 
 // Print Table of categories and items
 echo $OUTPUT->box_start('gradetreebox generalbox');
index 0efde54..b6dc052 100644 (file)
@@ -435,9 +435,9 @@ class grade_edit_tree {
         return $str;
     }
 
-    //Trims trailing zeros
-    //Used on the 'categories and items' page for grade items settings like aggregation co-efficient
-    //Grader report has its own decimal place settings so they are handled elsewhere
+    // Trims trailing zeros.
+    // Used on the 'Gradebook setup' page for grade items settings like aggregation co-efficient.
+    // Grader report has its own decimal place settings so they are handled elsewhere.
     static function format_number($number) {
         $formatted = rtrim(format_float($number, 4),'0');
         if (substr($formatted, -1)==get_string('decsep', 'langconfig')) { //if last char is the decimal point
index 84a8512..2216d57 100644 (file)
@@ -886,20 +886,6 @@ function grade_get_plugin_info($courseid, $active_type, $active_plugin) {
         }
     }
 
-    foreach ($plugin_info as $plugin_type => $plugins) {
-        if (!empty($plugins->id) && $active_plugin == $plugins->id) {
-            $plugin_info['strings']['active_plugin_str'] = $plugins->string;
-            break;
-        }
-        foreach ($plugins as $plugin) {
-            if (is_a($plugin, 'grade_plugin_info')) {
-                if ($active_plugin == $plugin->id) {
-                    $plugin_info['strings']['active_plugin_str'] = $plugin->string;
-                }
-            }
-        }
-    }
-
     return $plugin_info;
 }
 
@@ -2846,9 +2832,9 @@ abstract class grade_helper {
         $context = context_course::instance($courseid);
         self::$managesetting = array();
         if ($courseid != SITEID && has_capability('moodle/grade:manage', $context)) {
-            self::$managesetting['categoriesanditems'] = new grade_plugin_info('setup',
+            self::$managesetting['gradebooksetup'] = new grade_plugin_info('setup',
                 new moodle_url('/grade/edit/tree/index.php', array('id' => $courseid)),
-                get_string('categoriesanditems', 'grades'));
+                get_string('gradebooksetup', 'grades'));
             self::$managesetting['coursesettings'] = new grade_plugin_info('coursesettings',
                 new moodle_url('/grade/edit/settings/index.php', array('id'=>$courseid)),
                 get_string('coursegradesettings', 'grades'));
@@ -2887,6 +2873,12 @@ abstract class grade_helper {
                 continue;
             }
 
+            // Singleview doesn't doesn't accomodate for all cap combos yet, so this is hardcoded..
+            if ($plugin === 'singleview' && !has_all_capabilities(array('moodle/grade:viewall',
+                    'moodle/grade:edit'), $context)) {
+                continue;
+            }
+
             $pluginstr = get_string('pluginname', 'gradereport_'.$plugin);
             $url = new moodle_url('/grade/report/'.$plugin.'/index.php', array('id'=>$courseid));
             $gradereports[$plugin] = new grade_plugin_info($plugin, $url, $pluginstr);
index 244af2f..88742ea 100644 (file)
@@ -590,7 +590,8 @@ class grade_report_grader extends grade_report {
 
         $showuserimage = $this->get_pref('showuserimage');
         $canseeuserreport = has_capability('gradereport/'.$CFG->grade_profilereport.':view', $this->context);
-        $canseesingleview = has_capability('gradereport/singleview:view', $this->context);
+        $canseesingleview = has_all_capabilities(array('gradereport/singleview:view', 'moodle/grade:viewall',
+            'moodle/grade:edit'), $this->context);
         $hasuserreportcell = $canseeuserreport || $canseesingleview;
 
         $strfeedback  = $this->get_lang_string("feedback");
@@ -836,7 +837,9 @@ class grade_report_grader extends grade_report {
                     }
 
                     $singleview = '';
-                    if (has_capability('gradereport/singleview:view', $this->context)) {
+                    if (has_all_capabilities(array('gradereport/singleview:view', 'moodle/grade:viewall',
+                        'moodle/grade:edit'), $this->context)) {
+
                         $url = new moodle_url('/grade/report/singleview/index.php', array(
                             'id' => $this->course->id,
                             'item' => 'grade',
index 5ecb430..cd4fead 100644 (file)
@@ -52,7 +52,7 @@ class gradereport_singleview extends grade_report {
      * @return array List of warnings
      */
     public function process_data($data) {
-        if (has_capability('moodle/grade:manage', $this->context)) {
+        if (has_capability('moodle/grade:edit', $this->context)) {
             return $this->screen->process($data);
         }
     }
index d6928fa..0f35f82 100644 (file)
@@ -731,7 +731,12 @@ class grade_report_user extends grade_report {
                 if ($gradecat->aggregation == GRADE_AGGREGATE_SUM) {
                     // Natural aggregation/Sum of grades does not consider the mingrade, cannot traditionnally normalise it.
                     $graderange = $this->aggregationhints[$itemid]['grademax'];
-                    $gradeval = $this->aggregationhints[$itemid]['grade'] / $graderange;
+
+                    if ($graderange != 0) {
+                        $gradeval = $this->aggregationhints[$itemid]['grade'] / $graderange;
+                    } else {
+                        $gradeval = 0;
+                    }
                 } else {
                     $gradeval = grade_grade::standardise_score($this->aggregationhints[$itemid]['grade'],
                         $this->aggregationhints[$itemid]['grademin'], $this->aggregationhints[$itemid]['grademax'], 0, 1);
index e9cbcbd..727aeb0 100644 (file)
@@ -80,7 +80,8 @@ class behat_grade extends behat_base {
     }
 
     /**
-     * Sets a calculated manual grade item. Needs a table with item name - idnumber relation. The step requires you to be in categories and items page.
+     * Sets a calculated manual grade item. Needs a table with item name - idnumber relation.
+     * The step requires you to be in the 'Gradebook setup' page.
      *
      * @Given /^I set "(?P<calculation_string>(?:[^"]|\\")*)" calculation for grade item "(?P<grade_item_string>(?:[^"]|\\")*)" with idnumbers:$/
      * @param string $calculation The calculation.
@@ -139,7 +140,7 @@ class behat_grade extends behat_base {
 
     /**
      * Sets a calculated manual grade category total. Needs a table with item name - idnumber relation.
-     * The step requires you to be in categories and items page.
+     * The step requires you to be in the 'Gradebook setup' page.
      *
      * @Given /^I set "(?P<calculation_string>(?:[^"]|\\")*)" calculation for grade category "(?P<grade_item_string>(?:[^"]|\\")*)" with idnumbers:$/
      * @param string $calculation The calculation.
index d10d14a..09f1b04 100644 (file)
@@ -235,7 +235,7 @@ Feature: We can use calculated grade totals
       | itemname              | course | outcome | gradetype | scale      |
       | Test outcome item one | C1     | OT1     | Scale     | Test Scale |
     And I expand "Setup" node
-    And I follow "Categories and items"
+    And I follow "Gradebook setup"
     And I set the following settings for grade item "Course 1":
       | Aggregation                     | Natural |
       | Include outcomes in aggregation | 1       |
@@ -258,7 +258,7 @@ Feature: We can use calculated grade totals
     And I follow "Course 1"
     And I navigate to "Grades" node in "Course administration"
     And I expand "Setup" node
-    And I follow "Categories and items"
+    And I follow "Gradebook setup"
     And I set the following settings for grade item "Test outcome item one":
      | Extra credit     | 1   |
     And I log out
@@ -272,7 +272,7 @@ Feature: We can use calculated grade totals
     And I follow "Course 1"
     And I navigate to "Grades" node in "Course administration"
     And I expand "Setup" node
-    And I follow "Categories and items"
+    And I follow "Gradebook setup"
     And I set the following settings for grade item "Course 1":
       | Aggregation                     | Natural |
       | Include outcomes in aggregation | 0       |
@@ -297,7 +297,7 @@ Feature: We can use calculated grade totals
       | Test outcome item one | C1     | OT1     | Scale     | Test Scale |
     And I navigate to "Grades" node in "Course administration"
     And I expand "Setup" node
-    And I follow "Categories and items"
+    And I follow "Gradebook setup"
     And I set the following settings for grade item "Course 1":
       | Aggregation                     | Natural |
       | Include outcomes in aggregation | 1       |
@@ -387,7 +387,7 @@ Feature: We can use calculated grade totals
     And I set the following settings for grade item "Course 1":
       | Aggregation          | Natural |
       | Exclude empty grades | 0       |
-    And I navigate to "Categories and items" node in "Grade administration > Setup"
+    And I navigate to "Gradebook setup" node in "Grade administration > Setup"
     And I press "Add category"
     And I set the following fields to these values:
       | Category name | Sub category 3 |
@@ -441,7 +441,7 @@ Feature: We can use calculated grade totals
     And I press "Save changes"
     And I turn editing mode off
     And I should see "250.00 (25.25 %)" in the ".course" "css_element"
-    And I navigate to "Categories and items" node in "Grade administration > Setup"
+    And I navigate to "Gradebook setup" node in "Grade administration > Setup"
     And I press "Add category"
     And I set the following fields to these values:
       | Category name | Sub sub category 1 |
@@ -452,7 +452,7 @@ Feature: We can use calculated grade totals
 
   @javascript
   Scenario: Natural aggregation from the setup screen
-    And I select "Categories and items" from the "Grade report" singleselect
+    And I select "Gradebook setup" from the "Grade report" singleselect
     And I set the following settings for grade item "Course 1":
       | Aggregation          | Natural |
     And I set the following settings for grade item "Sub category 1":
@@ -514,7 +514,7 @@ Feature: We can use calculated grade totals
       | Aggregation          | Natural |
       | Exclude empty grades | 0       |
     And I turn editing mode off
-    And I select "Categories and items" from the "Grade report" singleselect
+    And I select "Gradebook setup" from the "Grade report" singleselect
     And I set the field "Override weight of Test assignment one" to "1"
     And I set the field "Weight of Test assignment one" to "0"
     And I set the field "Override weight of Test assignment six" to "1"
index 7453a73..fd792e0 100644 (file)
@@ -336,7 +336,7 @@ Feature: Changing the aggregation of an item affects its weight and extra credit
   @javascript
   Scenario: Switching grade items between categories
     # Move to same aggregation (Natural).
-    Given I navigate to "Categories and items" node in "Grade administration > Setup"
+    Given I navigate to "Gradebook setup" node in "Grade administration > Setup"
     And I set the field "Select Item a1" to "1"
     And I set the field "Select Item a2" to "1"
     And I set the field "Select Item a3" to "1"
@@ -362,7 +362,7 @@ Feature: Changing the aggregation of an item affects its weight and extra credit
     And the field "Extra credit" matches value "1"
     And I press "Cancel"
     # Move to Mean of grades (with extra credit).
-    And I navigate to "Categories and items" node in "Grade administration > Setup"
+    And I navigate to "Gradebook setup" node in "Grade administration > Setup"
     And I set the field "Select Item a1" to "1"
     And I set the field "Select Item a2" to "1"
     And I set the field "Select Item a3" to "1"
@@ -382,7 +382,7 @@ Feature: Changing the aggregation of an item affects its weight and extra credit
     And the field "Extra credit" matches value "1"
     And I press "Cancel"
     # Move to Simple weight mean of grades.
-    And I navigate to "Categories and items" node in "Grade administration > Setup"
+    And I navigate to "Gradebook setup" node in "Grade administration > Setup"
     And I set the field "Select Item a1" to "1"
     And I set the field "Select Item a2" to "1"
     And I set the field "Select Item a3" to "1"
@@ -402,7 +402,7 @@ Feature: Changing the aggregation of an item affects its weight and extra credit
     And the field "Extra credit" matches value "1"
     And I press "Cancel"
     # Move to Weighted mean of grades.
-    And I navigate to "Categories and items" node in "Grade administration > Setup"
+    And I navigate to "Gradebook setup" node in "Grade administration > Setup"
     And I set the field "Select Item a1" to "1"
     And I set the field "Select Item a2" to "1"
     And I set the field "Select Item a3" to "1"
@@ -426,7 +426,7 @@ Feature: Changing the aggregation of an item affects its weight and extra credit
     And I set the field "Item weight" to "11"
     And I press "Save changes"
     # Move to same (Weighted mean of grades).
-    And I navigate to "Categories and items" node in "Grade administration > Setup"
+    And I navigate to "Gradebook setup" node in "Grade administration > Setup"
     And I set the field "Select Item a1" to "1"
     And I set the field "Select Item a2" to "1"
     And I set the field "Select Item a3" to "1"
@@ -447,7 +447,7 @@ Feature: Changing the aggregation of an item affects its weight and extra credit
     And the field "Item weight" matches value "11"
     And I press "Save changes"
     # Move back to Natural.
-    And I navigate to "Categories and items" node in "Grade administration > Setup"
+    And I navigate to "Gradebook setup" node in "Grade administration > Setup"
     And I set the field "Select Item a1" to "1"
     And I set the field "Select Item a2" to "1"
     And I set the field "Select Item a3" to "1"
index 1899e4d..e52ab6a 100644 (file)
@@ -30,7 +30,7 @@ Feature: Average grades are displayed in the gradebook
       | Show average | Show |
     And I press "Save changes"
     # Add a manual grade item
-    And I navigate to "Categories and items" node in "Grade administration > Setup"
+    And I navigate to "Gradebook setup" node in "Grade administration > Setup"
     And I press "Add grade item"
     And I set the following fields to these values:
       | Item name | Manual item 1 |
index e295c02..145a39e 100644 (file)
@@ -2,7 +2,7 @@
 Feature: Calculated grade items can be used in the gradebook
   In order to use calculated grade items in the gradebook
   As a teacher
-  I need setup calculated grade items in the categories and items page.
+  I need setup calculated grade items in the 'Gradebook setup' page.
 
   Background:
     Given the following "courses" exist:
@@ -22,7 +22,7 @@ Feature: Calculated grade items can be used in the gradebook
     And I am on site homepage
     And I follow "Course 1"
     And I navigate to "Grades" node in "Course administration"
-    And I navigate to "Categories and items" node in "Grade administration > Setup"
+    And I navigate to "Gradebook setup" node in "Grade administration > Setup"
 
   @javascript
   Scenario: The max grade for a category item, with a calculation using Natural aggregation, can be changed
@@ -77,7 +77,7 @@ Feature: Calculated grade items can be used in the gradebook
       | grade item 1                        | -                 | 75.00  | 0–100 | 75.00 %    | -                            |
       | Calc cat totalInclude empty grades. | 100.00 %          | 37.50  | 0–50  | 75.00 %    | -                            |
       | Course total                        | -                 | 37.50  | 0–50  | 75.00 %    | -                            |
-    And I navigate to "Categories and items" node in "Grade administration > Setup"
+    And I navigate to "Gradebook setup" node in "Grade administration > Setup"
     And I set the following settings for grade item "Calc cat":
       | Maximum grade | 40 |
     And I follow "Grader report"
@@ -143,7 +143,7 @@ Feature: Calculated grade items can be used in the gradebook
       | grade item 1 | 66.67 %           | 75.00  | 0–100 | 75.00 %    | 50.00 %                      |
       | calc item    | 33.33 %           | 37.50  | 0–50  | 75.00 %    | 25.00 %                      |
       | Course total | -                 | 112.50 | 0–150 | 75.00 %    | -                            |
-    And I navigate to "Categories and items" node in "Grade administration > Setup"
+    And I navigate to "Gradebook setup" node in "Grade administration > Setup"
     And I set the following settings for grade item "calc item":
       | Maximum grade | 40 |
     And I follow "Grader report"
index 62d5e3e..cd8d791 100644 (file)
@@ -23,7 +23,7 @@ Feature: Gradebook calculations for calculated grade items before the fix 201506
     And I am on site homepage
     And I follow "Course 1"
     And I navigate to "Grades" node in "Course administration"
-    And I navigate to "Categories and items" node in "Grade administration > Setup"
+    And I navigate to "Gradebook setup" node in "Grade administration > Setup"
 
   @javascript
   Scenario: The max grade for a category item, with a calculation using Natural aggregation, can be changed
@@ -78,7 +78,7 @@ Feature: Gradebook calculations for calculated grade items before the fix 201506
       | grade item 1                        | -                 | 75.00  | 0–100 | 75.00 %    | -                            |
       | Calc cat totalInclude empty grades. | 100.00 %          | 37.50  | 0–100 | 37.50 %    | -                            |
       | Course total                        | -                 | 37.50  | 0–100 | 37.50 %    | -                            |
-    And I navigate to "Categories and items" node in "Grade administration > Setup"
+    And I navigate to "Gradebook setup" node in "Grade administration > Setup"
     And I set the following settings for grade item "Calc cat":
       | Maximum grade | 40 |
     And I follow "Grader report"
@@ -144,7 +144,7 @@ Feature: Gradebook calculations for calculated grade items before the fix 201506
       | grade item 1 | 50.00 %           | 75.00  | 0–100 | 75.00 %    | 37.50 %                      |
       | calc item    | 50.00 %           | 37.50  | 0–100 | 37.50 %    | 18.75 %                      |
       | Course total | -                 | 112.50 | 0–200 | 56.25 %    | -                            |
-    And I navigate to "Categories and items" node in "Grade administration > Setup"
+    And I navigate to "Gradebook setup" node in "Grade administration > Setup"
     And I set the following settings for grade item "calc item":
       | Maximum grade | 40 |
     And I follow "Grader report"
index e914de5..e899835 100644 (file)
@@ -43,7 +43,7 @@ Feature: We can understand the gradebook user report
     And I set the field "Show weightings" to "Show"
     And I set the field "Show contribution to course total" to "Show"
     And I press "Save changes"
-    And I set the field "Grade report" to "Categories and items"
+    And I set the field "Grade report" to "Gradebook setup"
     And I press "Add category"
     And I set the field "Category name" to "Sub category"
     And I press "Save changes"
index 896c567..7f60a15 100644 (file)
@@ -22,7 +22,7 @@ Feature: Extra credit contributions are normalised when going out of bounds
     And I am on site homepage
     And I follow "Course 1"
     And I navigate to "Grades" node in "Course administration"
-    And I navigate to "Categories and items" node in "Grade administration > Setup"
+    And I navigate to "Gradebook setup" node in "Grade administration > Setup"
     And I press "Add grade item"
     And I set the following fields to these values:
       | Item name | Manual item 1 |
@@ -56,7 +56,7 @@ Feature: Extra credit contributions are normalised when going out of bounds
     And I press "Save changes"
 
   Scenario Outline: The contribution of extra credit items is normalised
-    Given I set the field "Grade report" to "Categories and items"
+    Given I set the field "Grade report" to "Gradebook setup"
     When I set the following settings for grade item "Course 1":
       | Aggregation | <aggregation> |
     And I set the following settings for grade item "Manual item 2":
index 919b745..a886c39 100644 (file)
@@ -28,7 +28,7 @@ Feature: We can use a minimum grade different than zero
     And I am on site homepage
     And I follow "Course 1"
     And I navigate to "Grades" node in "Course administration"
-    And I navigate to "Categories and items" node in "Grade administration > Setup"
+    And I navigate to "Gradebook setup" node in "Grade administration > Setup"
     And I press "Add grade item"
     And I set the following fields to these values:
       | Item name | Manual item 1 |
@@ -73,7 +73,7 @@ Feature: We can use a minimum grade different than zero
 
   @javascript
   Scenario: Natural aggregation with negative and positive grade
-    And I navigate to "Categories and items" node in "Grade administration > Setup"
+    And I navigate to "Gradebook setup" node in "Grade administration > Setup"
     And I set the following settings for grade item "Sub category 1":
       | Aggregation          | Natural |
       | Exclude empty grades | 0       |
index b956a5a..cb1aa15 100644 (file)
@@ -27,7 +27,7 @@ Feature: We can choose what min or max grade to use when aggregating grades.
     And I am on site homepage
     And I follow "C1"
     And I navigate to "Grades" node in "Course administration"
-    And I navigate to "Categories and items" node in "Grade administration > Setup"
+    And I navigate to "Gradebook setup" node in "Grade administration > Setup"
     And I press "Add grade item"
     And I set the following fields to these values:
       | Item name | MI 1 |
@@ -62,7 +62,7 @@ Feature: We can choose what min or max grade to use when aggregating grades.
     And I set the field "Show weightings" to "Show"
     And I set the field "Show contribution to course total" to "Show"
     And I press "Save changes"
-    And I navigate to "Categories and items" node in "Grade administration > Setup"
+    And I navigate to "Gradebook setup" node in "Grade administration > Setup"
     And I set the following settings for grade item "CAT1":
       | Aggregation          | Natural |
     And I log out
@@ -98,7 +98,7 @@ Feature: We can choose what min or max grade to use when aggregating grades.
       | MI 5         | 33.33 %           | 30.00  | 0–100 | 30.00 %    | 10.00 %                      |
       | CAT1 total   | 33.33 %           | 10.00  | 0–100 | 10.00 %    | -                            |
       | Course total | -                 | 60.00  | 0–300 | 20.00 %    | -                            |
-    And I navigate to "Categories and items" node in "Grade administration > Setup"
+    And I navigate to "Gradebook setup" node in "Grade administration > Setup"
     And I set the following settings for grade item "MI 1":
       | Maximum grade          | 50.00 |
       | Minimum grade          | 5.00 |
@@ -126,7 +126,7 @@ Feature: We can choose what min or max grade to use when aggregating grades.
       | MI 5         | 50.00 %           | 30.00  | 0–100 | 30.00 %    | 15.00 %                      |
       | CAT1 total   | 25.00 %           | 10.00  | 0–50  | 20.00 %    | -                            |
       | Course total | -                 | 60.00  | 0–200 | 30.00 %    | -                            |
-    And I navigate to "Categories and items" node in "Grade administration > Setup"
+    And I navigate to "Gradebook setup" node in "Grade administration > Setup"
     And I set the following settings for grade item "MI 5":
       | Maximum grade          | 200.00 |
     And I follow "User report"
index de2f83a..22c35aa 100644 (file)
@@ -26,7 +26,7 @@ Feature: Weights in natural aggregation are adjusted if the items are excluded f
     And I log in as "teacher1"
     And I follow "Course 1"
     And I navigate to "Grades" node in "Course administration"
-    And I set the field "Grade report" to "Categories and items"
+    And I set the field "Grade report" to "Gradebook setup"
     And I set the following settings for grade item "Test assignment four (extra)":
       | Extra credit | 1 |
     And I set the following settings for grade item "Test assignment five (extra)":
index b4b97cf..e3722f3 100644 (file)
@@ -27,7 +27,7 @@ Feature: Gradebook calculations for extra credit items before the fix 20150619
     And I log in as "teacher1"
     And I follow "Course 1"
     And I navigate to "Grades" node in "Course administration"
-    And I set the field "Grade report" to "Categories and items"
+    And I set the field "Grade report" to "Gradebook setup"
     And I set the following settings for grade item "Test assignment four (extra)":
       | Extra credit | 1 |
     And I set the following settings for grade item "Test assignment five (extra)":
index bd7ea43..6f8a16b 100644 (file)
@@ -33,7 +33,7 @@ Feature: We can use natural aggregation and weights will be normalised to a tota
     And I log in as "teacher1"
     And I follow "Course 1"
     And I navigate to "Grades" node in "Course administration"
-    And I set the field "Grade report" to "Categories and items"
+    And I set the field "Grade report" to "Gradebook setup"
 
   @javascript
   Scenario: Setting all weights in a category to exactly one hundred in total.
index b46f33f..6b488e1 100644 (file)
@@ -34,7 +34,7 @@ Feature: Gradebook calculations for natural weights normalisation before the fix
     And I log in as "teacher1"
     And I follow "Course 1"
     And I navigate to "Grades" node in "Course administration"
-    And I set the field "Grade report" to "Categories and items"
+    And I set the field "Grade report" to "Gradebook setup"
 
   @javascript
   Scenario: Grade items weights are normalised when all grade item weights are overridden (sum exactly 100). Extra credit is set to zero (before the fix 20150619).
index 06083dc..5662264 100644 (file)
@@ -84,7 +84,7 @@ Feature: We can change the grading type and maximum grade point values
     And I set the field "grade[modgrade_type]" to "Point"
     And I set the field "grade[modgrade_point]" to "20000"
     And I press "Save and display"
-    Then I should see "Invalid Grade Value. This must be an integer between 0 and 900"
+    Then I should see "Invalid Grade Value. This must be an integer between 1 and 900"
     And I press "Cancel"
 
   @javascript
@@ -104,5 +104,5 @@ Feature: We can change the grading type and maximum grade point values
     And I follow "Test Assignment 1"
     And I follow "Edit settings"
     And I press "Save and display"
-    Then I should see "Invalid Grade Value. This must be an integer between 0 and 100"
+    Then I should see "Invalid Grade Value. This must be an integer between 1 and 100"
     And I press "Cancel"
index 5cd48b0..ed42b97 100644 (file)
@@ -90,7 +90,7 @@ Feature: View gradebook when scales are used
       | Test assignment one | C     | F–A   | 50.00 %    | 60.00 %                      |
       | Sub category 1 total      | 3.00  | 0–5   | 60.00 %    | -                            |
       | Course total        | 3.00  | 0–5   | 60.00 %    | -                            |
-    And I select "Categories and items" from the "Grade report" singleselect
+    And I select "Gradebook setup" from the "Grade report" singleselect
     And the following should exist in the "grade_edit_tree_table" table:
       | Name                | Max grade |
       | Test assignment one | 5.00      |
@@ -136,7 +136,7 @@ Feature: View gradebook when scales are used
       | Test assignment one          | C              | F–A   | 50.00 %       | <contrib3>                   |
       | Sub category (<aggregation>) total<aggregation>. | 3.00           | 1–5   | 50.00 %       | -                            |
       | Course total<aggregation>.   | <coursetotal3> | 0–100 | <courseperc3> | -                            |
-    And I select "Categories and items" from the "Grade report" singleselect
+    And I select "Gradebook setup" from the "Grade report" singleselect
     And the following should exist in the "grade_edit_tree_table" table:
       | Name                | Max grade |
       | Test assignment one | A (5)     |
index b71b035..2fec297 100644 (file)
@@ -90,7 +90,7 @@ Feature: Control the aggregation of the scales
     And I turn editing mode on
     When I set the following settings for grade item "Course 1":
       | Aggregation | Natural |
-    And I navigate to "Categories and items" node in "Grade administration > Setup"
+    And I navigate to "Gradebook setup" node in "Grade administration > Setup"
     And I set the field "Override weight of Grade me" to "1"
     Then the field "Override weight of Grade me" matches value "100.00"
     And I click on "Edit" "link" in the "Scale me" "table_row"
@@ -102,7 +102,7 @@ Feature: Control the aggregation of the scales
       | grade_includescalesinaggregation | 1 |
     And I follow "Course 1"
     And I navigate to "Grades" node in "Course administration"
-    And I navigate to "Categories and items" node in "Grade administration > Setup"
+    And I navigate to "Gradebook setup" node in "Grade administration > Setup"
     And I set the field "Override weight of Grade me" to "1"
     And the field "Override weight of Grade me" matches value "95.238"
     And I set the field "Override weight of Scale me" to "1"
index 867c70c..190e1f7 100644 (file)
@@ -79,7 +79,7 @@ Feature: View gradebook when single item scales are used
       | Test assignment one | -     | Ace!–Ace! | -                            |
       | Sub category 1 total| -     | 0–1       | -                            |
       | Course total        | -     | 0–1       | -                            |
-    And I set the field "jump" to "Categories and items"
+    And I set the field "jump" to "Gradebook setup"
     And the following should exist in the "grade_edit_tree_table" table:
       | Name                | Max grade |
       | Test assignment one | 1.00      |
@@ -115,7 +115,7 @@ Feature: View gradebook when single item scales are used
       | Test assignment one                              | Ace!           | Ace!–Ace!   | <contrib1>                   |
       | Sub category (<aggregation>) total<aggregation>. | <cattotal1>    | 0–100       | -                            |
       | Course total<aggregation>.                       | <coursetotal1> | 0–100       | -                            |
-    And I set the field "jump" to "Categories and items"
+    And I set the field "jump" to "Gradebook setup"
     And the following should exist in the "grade_edit_tree_table" table:
       | Name                         | Max grade |
       | Test assignment one          | Ace! (1)  |
index 9aa8684..2632fed 100644 (file)
@@ -80,7 +80,7 @@ Feature: We can enter in grades and view reports from the gradebook
     And "Course 1" row "Grade" column of "overview-grade" table should not contain "90.00"
 
   Scenario: We can add a weighting to a grade item and it is displayed properly in the user report
-    When I select "Categories and items" from the "Grade report" singleselect
+    When I select "Gradebook setup" from the "Grade report" singleselect
     And I set the following settings for grade item "Course 1":
       | Aggregation | Weighted mean of grades |
     And I set the field "Extra credit value for Test assignment name" to "0.72"
index aa51b27..cb96fa0 100644 (file)
@@ -30,7 +30,7 @@ require_once($CFG->dirroot.'/grade/edit/tree/lib.php');
 
 
 /**
- * Tests grade_edit_tree (deals with the data on the categories and items page in the gradebook)
+ * Tests grade_edit_tree (deals with the data on the 'Gradebook setup' page in the gradebook)
  */
 class core_grade_edittreelib_testcase extends advanced_testcase {
     public function test_format_number() {
index 6d114c5..173ab92 100644 (file)
@@ -206,7 +206,6 @@ foreach ($members as $gpgid=>$groupdata) {
         echo $OUTPUT->heading($groupings[$gpgid]->formattedname, 3);
         $description = file_rewrite_pluginfile_urls($groupings[$gpgid]->description, 'pluginfile.php', $context->id, 'grouping', 'description', $gpgid);
         $options = new stdClass;
-        $options->noclean = true;
         $options->overflowdiv = true;
         echo $OUTPUT->box(format_text($description, $groupings[$gpgid]->descriptionformat, $options), 'generalbox boxwidthnarrow boxaligncenter');
     }
index 55c941d..24a8af4 100644 (file)
 
 defined('MOODLE_INTERNAL') || die();
 
-$string['admindirname'] = 'Dossier d\'administration';
-$string['dataroot'] = 'Dossier de données';
 $string['dbprefix'] = 'Préfixe des tables';
-$string['dirroot'] = 'Dossier Moodle';
 $string['installation'] = 'Installation';
-$string['memorylimithelp'] = '<p>La limite de mémoire de PHP sur votre serveur est actuellement de {$a}.</p> <p>Cette valeur très faible risque de générer des problèmes de manque de mémoire pour Moodle, notamment si vous utilisez beaucoup de modules et/ou si vous avez un grand nombre d\'utilisateurs.</p> <p>Il est recommandé de configurer PHP avec une limite de mémoire aussi élevée que possible, par exemple 16 Mo. Vous pouvez obtenir cela de différentes façons :
-<ol>
-<li>si vous en avez la possibilité, recompilez PHP avec l\'option <i>--enable-memory-limit</i>. Cela permettra à Moodle de fixer lui-même sa limite de mémoire ;</li>
-<li>si vous avez accès à votre fichier « php.ini », vous pouvez attribuer au paramètre <b>memory_limit</b> une valeur comme 40M. Si vous n\'y avez pas accès, demandez à l\'administrateur de le faire pour vous ;</li>
-<li>sur certains serveur, vous pouvez créer dans le dossier principal de Moodle un fichier « .htaccess » contenant cette ligne : <p><blockquote>php_value memory_limit 40M</blockquote></p> <p>Cependant, sur certains serveur, cela empêchera le fonctionnement correcte de <b>tous</b> les fichiers PHP (vous verrez s\'afficher des erreurs lors de la consultation de pages). Dans ce cas, vous devrez supprimer le fichier « .htaccess ».</li>
-</ol>';
 $string['phpversion'] = 'Version de PHP';
-$string['phpversionhelp'] = '<p>Moodle nécessite au minimum la version 4.1.0 de PHP.</p> <p>Vous utilisez actuellement la version {$a}.</p> <p>Pour que Moodle fonctionne, vous devez mettre à jour PHP ou aller chez un hébergeur ayant une version récente de PHP.</p>';
 $string['wwwroot'] = 'Adresse web';
similarity index 60%
rename from blocks/tags/settings.php
rename to install/lang/xct/langconfig.php
index 4db5098..5cc0b59 100644 (file)
@@ -1,4 +1,5 @@
 <?php
+
 // This file is part of Moodle - http://moodle.org/
 //
 // Moodle is free software: you can redistribute it and/or modify
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
 /**
- * Settings for the tags block.
+ * Automatically generated strings for Moodle installer
+ *
+ * Do not edit this file manually! It contains just a subset of strings
+ * needed during the very first steps of installation. This file was
+ * generated automatically by export-installer.php (which is part of AMOS
+ * {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
+ * list of strings defined in /install/stringnames.txt.
  *
- * @package   block_tags
- * @copyright 1999 onwards Martin Dougiamas  {@link http://moodle.com}
+ * @package   installer
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
-defined('MOODLE_INTERNAL') || die;
+defined('MOODLE_INTERNAL') || die();
 
-if ($ADMIN->fulltree) {
-    $settings->add(new admin_setting_configcheckbox('block_tags_showcoursetags', get_string('showcoursetags', 'block_tags'),
-                       get_string('showcoursetagsdef', 'block_tags'), 0));
-}
+$string['thislanguage'] = 'བོད་ཡིག';
index 7e7f4c7..99715f5 100644 (file)
@@ -900,7 +900,7 @@ $string['registermoodleorgli1'] = 'You are added to a low-volume mailing list fo
 $string['registermoodleorgli2'] = 'Statistics about your site will be added to the {$a} of the worldwide Moodle community.';
 $string['registerwithmoodleorg'] = 'Register your site';
 $string['registration'] = 'Registration';
-$string['registration_help'] = 'Registering your site with Moodle.org is recommended in order to receive security alert notifications, to contribute <a href="http://moodle.org/stats">Moodle usage statistics</a> and to be able to share courses on <a href="http://moodle.net/">Moodle.net</a>.';
+$string['registration_help'] = 'It is recommended that you register your site in order to receive security alerts and access to Moodle.net, our course sharing platform.';
 $string['registrationwarning'] = 'Your site is not yet registered.';
 $string['releasenoteslink'] = 'For information about this version of Moodle, please see the online <a target="_blank" href="{$a}">Release Notes</a>';
 $string['rememberusername'] = 'Remember username';
@@ -1019,7 +1019,8 @@ $string['taskcachecleanup'] = 'Remove expired cache entries';
 $string['taskcachecron'] = 'Background processing for caches';
 $string['taskcalendarcron'] = 'Send calendar notifications';
 $string['taskcheckforupdates'] = 'Check for updates';
-$string['taskcompletioncron'] = 'Calculate completion data';
+$string['taskcompletionregular'] = 'Calculate regular completion data';
+$string['taskcompletiondaily'] = 'Completion mark as started';
 $string['taskcontextcleanup'] = 'Cleanup contexts';
 $string['taskcreatecontexts'] = 'Create missing contexts';
 $string['taskdeletecachetext'] = 'Delete old text cache records';
index 93f82df..ab59bdb 100644 (file)
@@ -10,4 +10,13 @@ myfilesmanage,core
 mypreferences,core_grades
 myprofile,core
 viewallmyentries,core_blog
-cannotdeletepost,core_notes
\ No newline at end of file
+cannotdeletepost,core_notes
+addedotag,core_tag
+newname,core_tag
+tagtype_default,core_tag
+tagtype_official,core_tag
+thistaghasnodesc,core_tag
+updated,core_tag
+withselectedtags,core_tag
+tag:create,core_role
+categoriesanditems,core_grades
index 0d58b13..dd5ace3 100644 (file)
@@ -388,6 +388,7 @@ $string['movecatcontentstoroot'] = 'Moving the category content to root is not a
 $string['movecategorynotpossible'] = 'You cannot move category \'{$a}\' into the selected category.';
 $string['movecategoryownparent'] = 'You cannot make category \'{$a}\' a parent of itself.';
 $string['movecategoryparentconflict'] = 'You cannot make category \'{$a}\' a subcategory of one of its own subcategories.';
+$string['mssqlrcsmodemissing'] = 'The database is not using the expected READ_COMMITTED_SNAPSHOT mode which can lead to wrong results, especially under high concurrency scenarios. Please enable it for correct behaviour. You can find more information in the <a href="https://docs.moodle.org/en/Installing_MSSQL_for_PHP#Configuration">Moodle Docs</a>.';
 $string['multiplerecordsfound'] = 'Multiple records found, only one record expected.';
 $string['multiplerestorenotallow'] = 'Multiple restore execution not allowed!';
 $string['mustbeloggedin'] = 'You must be logged in to do this';
index 02676d6..da10569 100644 (file)
@@ -66,7 +66,7 @@ $string['aggregationhintexcluded'] = '( Excluded )';
 $string['aggregationhintextra'] = '( Extra credit )';
 $string['aggregation_link'] = 'grade/aggregation';
 $string['aggregationcoef'] = 'Aggregation coefficient';
-$string['aggregationcoefextra'] = 'Extra credit'; // for the header of the table at Edit categories and items page
+$string['aggregationcoefextra'] = 'Extra credit'; // For the header of the table on the 'Gradebook setup' page.
 $string['aggregationcoefextra_help'] = 'If the aggregation is \'Natural\' or \'Simple weighted mean\' and the extra credit checkbox is ticked, the grade item\'s maximum grade is not added to the category\'s maximum grade. This will result in the possibility of achieving the maximum grade in the category without having the maximum grade in all the grade items. If the site administrator has enabled grades over the maximum, there might be grades over the maximum.
 
 If the aggregation is \'Mean of grades (with extra credits)\' and the extra credit is set to a value greater than zero, the extra credit is the factor by which the grade is multiplied before adding it to the total after the computation of the mean.';
@@ -112,7 +112,6 @@ $string['calculationsaved'] = 'Calculation saved';
 $string['calculationview'] = 'View calculation';
 $string['cannotaccessgroup'] = 'Can not access grades of selected group, sorry.';
 $string['categories'] = 'Categories';
-$string['categoriesanditems'] = 'Categories and items';
 $string['category'] = 'Category';
 $string['categoryedit'] = 'Edit category';
 $string['categoryname'] = 'Category name';
@@ -244,6 +243,7 @@ $string['gradebookcalculationsfixbutton'] = 'Accept grade changes and fix calcul
 $string['gradebookcalculationswarning'] = 'Note: Some errors have been detected in calculating the grades displayed in the gradebook. It is recommended that the errors are fixed by clicking the button below, though this will result in some grades being changed. For details, see the changes between versions {$a->gradebookversion} and {$a->currentversion} in <a href="{$a->url}">Gradebook calculation changes</a>.';
 $string['gradebookhiddenerror'] = 'The gradebook is currently set to hide everything from students.';
 $string['gradebookhistories'] = 'Grade histories';
+$string['gradebooksetup'] = 'Gradebook setup';
 $string['gradeboundary'] = 'Letter grade boundary';
 $string['gradeboundary_help'] = 'This setting determines the minimum percentage over which grades will be assigned the grade letter.';
 $string['gradecategories'] = 'Grade categories';
@@ -470,7 +470,7 @@ $string['minimum_show_help'] = 'Minimum grade is used in calculating grades and
 $string['missingitemtypeoreid'] = 'Array key (itemtype or eid) missing from 2nd param of grade_edit_tree_column_select::get_item_cell($item, $params)';
 $string['missingscale'] = 'Scale must be selected';
 $string['mode'] = 'Mode';
-$string['modgradeerrorbadpoint'] = 'Invalid Grade Value. This must be an integer between 0 and {$a}';
+$string['modgradeerrorbadpoint'] = 'Invalid Grade Value. This must be an integer between 1 and {$a}';
 $string['modgradeerrorbadscale'] = 'Invalid scale selected. Please make sure you select a scale from the selections below.';
 $string['modgrade'] = 'Grade';
 $string['modgrade_help'] = 'Select the type of grading used for this activity. If "scale" is chosen, you can then choose the scale from the "scale" dropdown. If using "point" grading, you can then enter the maximum grade available for this activity.';
@@ -789,3 +789,6 @@ $string['writinggradebookinfo'] = 'Writing gradebook settings';
 $string['xml'] = 'XML';
 $string['yes'] = 'Yes';
 $string['yourgrade'] = 'Your grade';
+
+// Deprecated since 3.0.
+$string['categoriesanditems'] = 'Categories and items';
index 02d240b..3b5d5c8 100644 (file)
@@ -95,7 +95,7 @@ $string['errorcoursepublish'] = 'An error occurred during the course publication
 $string['errorcoursewronglypublished'] = 'A publication error has been returned by the hub. Please try again later.';
 $string['errorcron'] = 'An error occurred during registration update on "{$a->hubname}" ({$a->errormessage})';
 $string['errorcronnoxmlrpc'] = 'XML-RPC must be enabled in order to update the registration.';
-$string['errorhublisting'] = 'An error occurred when retrieving the hub listing from Moodle.org, please try again later. ({$a})';
+$string['errorhublisting'] = 'An error occurred when retrieving the hub listing from Moodle. Please try again later. ({$a})';
 $string['errorlangnotrecognized'] = 'The provided language code is unknown by Moodle. Please contact {$a}';
 $string['errorregistration'] = 'An error occurred during registration, please try again later. ({$a})';
 $string['errorunpublishcourses']= 'Due to an unexpected error, the courses could not be deleted on the hub. Try again later (recommended) or contact the hub administrator.';
@@ -118,7 +118,7 @@ $string['licence_help'] = 'Select the licence you want to distribute your course
 $string['licence_link'] = 'licenses';
 $string['logourl'] = 'Logo URL';
 $string['modulenumberaverage'] = 'Average number of course modules ({$a})';
-$string['moodleorg'] = 'Moodle.org';
+$string['moodleorg'] = 'Moodle';
 $string['mustselectsubject'] = 'You must select a subject';
 $string['name'] = 'Name';
 $string['name_help'] = 'This name will be showing in the course listing.';
@@ -158,9 +158,9 @@ $string['questionsnumber'] = 'Number of questions ({$a})';
 $string['registeredcourses'] = 'Registered courses';
 $string['registeredsites'] = 'Registered sites';
 $string['registrationinfo'] = 'Registration information';
-$string['registeredmoodleorg'] = 'Moodle.org ({$a})';
+$string['registeredmoodleorg'] = 'Moodle ({$a})';
 $string['registeredon'] = 'Where your site is registered';
-$string['registermoochtips'] = 'In order to register with Moodle.net, your site must be registered with Moodle.org.';
+$string['registermoochtips'] = 'Register your site with Moodle to get security alerts and access to Moodle.net, our course sharing platform.';
 $string['registersite'] = 'Register with {$a}';
 $string['registerwith'] = 'Register with a hub';
 $string['registrationconfirmed'] = 'Site registration confirmed';
index 15fd175..5ad5738 100644 (file)
@@ -38,7 +38,7 @@ $string['mediasettings'] = 'Media embedding';
 $string['mp3audio'] = 'MP3 audio';
 $string['mp3audio_desc'] = 'Files with extension *.mp3. Plays audio using Flowplayer, requires Flash plugin.';
 $string['legacyquicktime'] = 'QuickTime player';
-$string['legacyquicktime_desc'] = 'Files with extension *.mov, *.mp4, *.m4a, *.mp4 and *.mpg. Requires QuickTime player or codecs.';
+$string['legacyquicktime_desc'] = 'Files with extension *.mov, *.mp4, *.m4a and *.mpg. Requires QuickTime player or codecs.';
 $string['legacyreal'] = 'Real media player';
 $string['legacyreal_desc'] = 'Files with extension *.rm, *.ra, *.ram, *.rp and *.rv. Requires RealPlayer.';
 $string['legacywmp'] = 'Windows media player';
index dacd551..2acbcfa 100644 (file)
@@ -41,6 +41,8 @@ $string['contacts'] = 'Contacts';
 $string['context'] = 'context';
 $string['defaultmessageoutputs'] = 'Default message outputs';
 $string['defaults'] = 'Defaults';
+$string['deletemessage'] = 'Delete message';
+$string['deletemessageconfirmation'] = 'Are you sure you want to delete this message? It will only be deleted from your messaging history and will still be viewable by the user who sent or received the message.';
 $string['deletemessagesdays'] = 'Number of days before old messages are automatically deleted';
 $string['disableall'] = 'Temporarily disable notifications';
 $string['disableall_help'] = 'Temporarily disable all notifications except those marked as "forced" by the site administrator';
@@ -52,7 +54,7 @@ $string['emailtagline'] = 'This is a copy of a message sent to you at "{$a->site
 $string['emptysearchstring'] = 'You must search for something';
 $string['enabled'] = 'Enabled';
 $string['errorcallingprocessor'] = 'Error calling defined output';
-$string['errorwhilesendingmessage'] = 'An error occured while sending the message, please try again later.';
+$string['errorwhilesendingmessage'] = 'An error occurred while sending the message; please try again later.';
 $string['errortranslatingdefault'] = 'Error translating default setting provided by plugin, using system defaults instead.';
 $string['eventmessagecontactadded'] = 'Message contact added';
 $string['eventmessagecontactblocked'] = 'Message contact blocked';
index 78468c7..4a6e3fc 100644 (file)
@@ -1910,7 +1910,6 @@ $string['uploadserverlimit'] = 'Uploaded file exceeded the maximum size limit se
 $string['uploadthisfile'] = 'Upload this file';
 $string['url'] = 'URL';
 $string['used'] = 'Used';
-$string['userdetails'] = 'User details';
 $string['usedinnplaces'] = 'Used in {$a} places';
 $string['usemessageform'] = 'or use the form below to send a message to the selected students';
 $string['user'] = 'User';
index 126fb08..2ce0f2c 100644 (file)
@@ -195,6 +195,7 @@ $string['lasttry'] = 'Last try';
 $string['linkedfiledoesntexist'] = 'Linked file {$a} doesn\'t exist';
 $string['makechildof'] = 'Make child of \'{$a}\'';
 $string['maketoplevelitem'] = 'Move to top level';
+$string['manualgradeinvalidformat'] = 'That is not a valid number.';
 $string['matchgrades'] = 'Match grades';
 $string['matchgradeserror'] = 'Error if grade not listed';
 $string['matchgradesnearest'] = 'Nearest grade if not listed';
index c3e2378..4b2ee09 100644 (file)
@@ -107,13 +107,13 @@ $string['cohort:view'] = 'View site-wide cohorts';
 $string['cohort:manage'] = 'Create, delete and move cohorts';
 $string['comment:delete'] = 'Delete comments';
 $string['comment:post'] = 'Post comments';
-$string['comment:view'] = 'Read comments';
+$string['comment:view'] = 'View comments';
 $string['community:add'] = 'Use the community block to search hubs and find courses';
 $string['community:download'] = 'Download a course from the community block';
 $string['confirmaddadmin'] = 'Do you really want to add user <strong>{$a}</strong> as new site administrator?';
 $string['confirmdeladmin'] = 'Do you really want to remove user <strong>{$a}</strong> from the list of site administrators?';
-$string['confirmroleprevent'] = 'Do you really want to remove <strong>{$a->role}</strong> from the list of allowed roles for capability {$a->cap} in context {$a->context}?';
-$string['confirmroleunprohibit'] = 'Do you really want to remove <strong>{$a->role}</strong> from the list of prohibited roles for capability {$a->cap} in context {$a->context}?';
+$string['confirmroleprevent'] = 'Do you really want to remove <strong>"{$a->role}"</strong> from the list of allowed roles for capability "{$a->cap}" in context "{$a->context}"?';
+$string['confirmroleunprohibit'] = 'Do you really want to remove <strong>"{$a->role}"</strong> from the list of prohibited roles for capability "{$a->cap}" in context "{$a->context}"?';
 $string['confirmunassign'] = 'Are you sure you wish to remove this role from this user?';
 $string['confirmunassigntitle'] = 'Confirm role change';
 $string['confirmunassignyes'] = 'Remove';
@@ -146,6 +146,7 @@ $string['course:reset'] = 'Reset course';
 $string['course:reviewotherusers'] = 'Review other users';
 $string['course:sectionvisibility'] = 'Control section visibility';
 $string['course:setcurrentsection'] = 'Set current section';
+$string['course:tag'] = 'Change course tags';
 $string['course:update'] = 'Update course settings';
 $string['course:useremail'] = 'Enable/disable email address';
 $string['course:view'] = 'View courses without participation';
@@ -257,7 +258,7 @@ $string['noneinthisxmatching'] = 'No users matching \'{$a->search}\' in this {$a
 $string['norole'] = 'No role';
 $string['noroles'] = 'No roles';
 $string['noroleassignments'] = 'This user does not have any role assignments anywhere in this site.';
-$string['notabletoassignroleshere'] = 'You are not able to assign any roles here';
+$string['notabletoassignroleshere'] = 'Assigning of roles in this context has not been enabled by an administrator.';
 $string['notabletooverrideroleshere'] = 'You are not able to override the permissions on any roles here';
 $string['notes:manage'] = 'Manage notes';
 $string['notes:view'] = 'View notes';
@@ -321,7 +322,7 @@ $string['restore:userinfo'] = 'Restore user data';
 $string['restore:viewautomatedfilearea'] = 'Restore courses from automated backups';
 $string['risks'] = 'Risks';
 $string['roleallowheader'] = 'Allow role:';
-$string['roleallowinfo'] = 'Select a role to be added to the list of allowed roles in context {$a->context}, capability {$a->cap}:';
+$string['roleallowinfo'] = 'Select a role to be added to the list of allowed roles in context "{$a->context}", capability "{$a->cap}":';
 $string['role:assign'] = 'Assign roles to users';
 $string['roleassignments'] = 'Role assignments';
 $string['roledefinitions'] = 'Role definitions';
@@ -331,7 +332,7 @@ $string['role:manage'] = 'Create and manage roles';
 $string['role:override'] = 'Override permissions for others';
 $string['role:review'] = 'Review permissions for others';
 $string['roleprohibitheader'] = 'Prohibit role';
-$string['roleprohibitinfo'] = 'Select a role to be added to the list of prohibited roles in context {$a->context}, capability {$a->cap}:';
+$string['roleprohibitinfo'] = 'Select a role to be added to the list of prohibited roles in context "{$a->context}", capability "{$a->cap}":';
 $string['rolerisks'] = 'Role risks';
 $string['roles'] = 'Roles';
 $string['roles_help'] = 'A role is a collection of permissions defined for the whole system that you can assign to specific users in specific contexts.';
@@ -355,6 +356,8 @@ $string['siteadministrators'] = 'Site administrators';
 $string['site:approvecourse'] = 'Approve course creation';
 $string['site:backup'] = 'Backup courses';
 $string['site:config'] = 'Change site configuration';
+$string['site:deleteanymessage'] = 'Delete any messages on the site';
+$string['site:deleteownmessage'] = 'Delete messages sent by and to the user';
 $string['site:doanything'] = 'Allowed to do everything';
 $string['site:doclinks'] = 'Show links to offsite docs';
 $string['site:forcelanguage'] = 'Override course language';
@@ -371,7 +374,6 @@ $string['site:viewfullnames'] = 'Always see full names of users';
 $string['site:viewparticipants'] = 'View participants';
 $string['site:viewreports'] = 'View reports';
 $string['site:viewuseridentity'] = 'See full user identity in lists';
-$string['tag:create'] = 'Create new tags';
 $string['tag:edit'] = 'Edit existing tags';
 $string['tag:editblocks'] = 'Edit blocks in tags pages';
 $string['tag:manage'] = 'Manage all tags';
@@ -422,3 +424,6 @@ $string['whydoesusernothavecap'] = 'Why does {$a->fullname} not have capability
 $string['xroleassignments'] = '{$a}\'s role assignments';
 $string['xuserswiththerole'] = 'Users with the role "{$a->role}"';
 
+// Deprecated in 3.0.
+
+$string['tag:create'] = 'Create new tags';
index 4b27fc6..ef4be4b 100644 (file)
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
-$string['addedotag'] = '"{$a}" was added as an official tag.';
+$string['added'] = 'Official tag(s) added';
 $string['addotags'] = 'Add official tags';
 $string['addtagtomyinterests'] = 'Add "{$a}" to my interests';
 $string['alltagpages'] = 'All tag pages';
+$string['confirmdeletetag'] = 'Are you sure you want to delete this tag?';
+$string['confirmdeletetags'] = 'Are you sure you want to delete selected tags?';
 $string['count'] = 'Count';
+$string['coursetags'] = 'Course tags';
 $string['delete'] = 'Delete';
-$string['deleted'] = 'Deleted';
+$string['deleteselected'] = 'Delete selected';
+$string['deleted'] = 'Tag(s) deleted';
 $string['deletedcoursetags'] = 'Deleted - Course tags';
 $string['description'] = 'Description';
+$string['editname'] = 'Edit tag name';
 $string['edittag'] = 'Edit this tag';
 $string['entertags'] = 'Enter tags separated by commas';
 $string['errortagfrontpage'] = 'Tagging the site main page is not allowed';
@@ -43,6 +48,7 @@ $string['eventtagremoved'] = 'Tag removed from an item';
 $string['eventtagunflagged'] = 'Tag unflagged';
 $string['eventtagupdated'] = 'Tag updated';
 $string['flag'] = 'Flag';
+$string['flagged'] = 'Tag flagged';
 $string['flagasinappropriate'] = 'Flag as inappropriate';
 $string['helprelatedtags'] = 'Comma separated related tags';
 $string['changename'] = 'Change tag name';
@@ -52,9 +58,10 @@ $string['manageofficialtags'] = 'Manage official tags';
 $string['managetags'] = 'Manage tags';
 $string['name'] = 'Tag name';
 $string['namesalreadybeeingused'] = 'Tag names already being used';
-$string['newname'] = 'New tag name';
+$string['newnamefor'] = 'New name for tag {$a}';
 $string['noresultsfor'] = 'No results for "{$a}"';
-$string['officialtag'] = 'Official tag';
+$string['nothingtoupdate'] = 'Nothing to update';
+$string['officialtag'] = 'Official';
 $string['otags'] = 'Official tags';
 $string['othertags'] = 'Other tags (enter tags separated by commas)';
 $string['owner'] = 'Owner';
@@ -62,7 +69,7 @@ $string['ptags'] = 'User defined tags (Comma separated)';
 $string['relatedblogs'] = 'Most recent blog entries';
 $string['relatedtags'] = 'Related tags';
 $string['removetagfrommyinterests'] = 'Remove "{$a}" from my interests';
-$string['reset'] = 'Reset';
+$string['reset'] = 'Tag flag reset';
 $string['resetflag'] = 'Reset flag';
 $string['responsiblewillbenotified'] = 'The person responsible will be notified';
 $string['rssdesc'] = 'This RSS feed was automatically generated by Moodle and contains user generated tags for courses.';
@@ -72,23 +79,31 @@ $string['searchresultsfor'] = 'Search results for "{$a}"';
 $string['searchtags'] = 'Search tags';
 $string['seeallblogs'] = 'See all blog entries tagged with "{$a}"...';
 $string['select'] = 'Select';
+$string['selecttag'] = 'Select tag {$a}';
+$string['settypedefault'] = 'Remove from official tags';
+$string['settypeofficial'] = 'Make official';
 $string['tag'] = 'Tag';
 $string['tagdescription'] = 'Tag description';
 $string['taggedwith'] = 'tagged with "{$a}"';
 $string['tags'] = 'Tags';
 $string['tagsaredisabled'] = 'Tags are disabled';
 $string['tagtype'] = 'Tag type';
-$string['tagtype_default'] = 'Default';
-$string['tagtype_official'] = 'Official';
 $string['thingstaggedwith'] = '"{$a->name}" is used {$a->count} times';
 $string['thingtaggedwith'] = '"{$a->name}" is used once';
-$string['thistaghasnodesc'] = 'This tag currently has no description.';
 $string['timemodified'] = 'Modified';
 $string['typechanged'] = 'Tag type changed';
-$string['updated'] = 'Updated';
 $string['updatetag'] = 'Update';
-$string['withselectedtags'] = 'With selected tags...';
 $string['page-tag-x'] = 'All tag pages';
 $string['page-tag-index'] = 'Single tag page';
 $string['page-tag-search'] = 'Tag search page';
 $string['page-tag-manage'] = 'Manage tags page';
+
+// Deprecated since 3.0 .
+
+$string['addedotag'] = '"{$a}" was added as an official tag.';
+$string['newname'] = 'New tag name';
+$string['tagtype_default'] = 'Default';
+$string['tagtype_official'] = 'Official';
+$string['thistaghasnodesc'] = 'This tag currently has no description.';
+$string['updated'] = 'Updated';
+$string['withselectedtags'] = 'With selected tags...';
index e9360f0..3563fde 100644 (file)
@@ -2319,6 +2319,7 @@ class admin_setting_confightmleditor extends admin_setting_configtext {
         }
 
         $editor = editors_get_preferred_editor(FORMAT_HTML);
+        $editor->set_text($data);
         $editor->use_editor($this->get_id(), array('noclean'=>true));
 
         return format_admin_setting($this, $this->visiblename,
index 1cfd346..83625f7 100644 (file)
@@ -63,13 +63,12 @@ switch ($pagetype[0]) {
         $PAGE->set_blocks_editing_capability('moodle/my:manageblocks');
         break;
     case 'user':
-        if ($pagelayout == 'mydashboard') {
-            // If it's not the current user's profile, we need a different capability.
-            if ($PAGE->context->contextlevel == CONTEXT_USER && $PAGE->context->instanceid != $USER->id) {
-                $PAGE->set_blocks_editing_capability('moodle/user:manageblocks');
-            } else {
-                $PAGE->set_blocks_editing_capability('moodle/user:manageownblocks');
-            }
+        if ($pagetype[1] === 'profile' && $PAGE->context->contextlevel == CONTEXT_USER
+                && $PAGE->context->instanceid == $USER->id) {
+            // A user can only move blocks on their own site profile.
+            $PAGE->set_blocks_editing_capability('moodle/user:manageownblocks');
+        } else {
+            $PAGE->set_blocks_editing_capability('moodle/user:manageblocks');
         }
         break;
 }
diff --git a/lib/amd/build/permissionmanager.min.js b/lib/amd/build/permissionmanager.min.js
new file mode 100644 (file)
index 0000000..9f49f4a
Binary files /dev/null and b/lib/amd/build/permissionmanager.min.js differ
diff --git a/lib/amd/build/tag.min.js b/lib/amd/build/tag.min.js
new file mode 100644 (file)
index 0000000..8d684ae
Binary files /dev/null and b/lib/amd/build/tag.min.js differ
index 62d34b1..ed4aa32 100644 (file)
Binary files a/lib/amd/build/templates.min.js and b/lib/amd/build/templates.min.js differ
diff --git a/lib/amd/src/permissionmanager.js b/lib/amd/src/permissionmanager.js
new file mode 100644 (file)
index 0000000..267f6cb
--- /dev/null
@@ -0,0 +1,260 @@
+// 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/>.
+/*
+ * @package    core
+ * @class      permissionmanager
+ * @copyright  2015 Martin Mastny <mastnym@vscht.cz>
+ * @since      3.0
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+ /**
+  * @module admin/permissionmanager
+  */
+define(['jquery', 'core/config','core/notification', 'core/templates'], function($, config, notification, templates) {
+
+     /**
+      * Used CSS selectors
+      * @access private
+      */
+    var SELECTORS = {
+        ADDROLE: 'a.allowlink, a.prohibitlink',
+        REMOVEROLE: 'a.preventlink, a.unprohibitlink',
+        UNPROHIBIT: 'a.unprohibitlink'
+        };
+    var rolesloadedevent = $.Event('rolesloaded');
+    var contextid;
+    var contextname;
+    var adminurl;
+    var overideableroles;
+    var panel = null;
+
+    /**
+     * Load all possible roles, which could be assigned from server
+     *
+     * @access private
+     * @method loadOverideableRoles
+     */
+    var loadOverideableRoles = function() {
+        var params = {
+            contextid: contextid,
+            getroles: 1,
+            sesskey: config.sesskey
+        };
+
+        $.post(adminurl + 'roles/ajax.php', params, function() {})
+            .done(function(data) {
+              try {
+                  overideableroles = data;
+                  loadOverideableRoles = function() {
+                      $('body').trigger(rolesloadedevent);
+                  };
+                  loadOverideableRoles();
+              }
+              catch(err) {
+                  notification.exception(err);
+              }
+            })
+            .fail(function(jqXHR, status, error) {
+                notification.exception(error);
+            });
+    };
+
+    /**
+     * Perform the UI changes after server change
+     *
+     * @access private
+     * @method changePermissions
+     * @param {jquery node} row
+     * @param {int} roleid
+     * @param {string} action
+     */
+    var changePermissions = function(row, roleid, action) {
+        var params = {
+            contextid: contextid,
+            roleid: roleid,
+            sesskey: M.cfg.sesskey,
+            action: action,
+            capability: row.data('name')
+        };
+        $.post(adminurl + 'roles/ajax.php', params, function() {})
+        .done(function(data) {
+            var action = data;
+            try {
+                var templatedata = {rolename: overideableroles[roleid],
+                                    roleid: roleid,
+                                    adminurl: adminurl,
+                                    imageurl: M.util.image_url('t/delete', 'moodle')
+                                    };
+                switch (action) {
+                    case 'allow':
+                        templatedata.spanclass = 'allowed';
+                        templatedata.linkclass = 'preventlink';
+                        templatedata.action = 'prevent';
+                        break;
+                    case 'prohibit':
+                        templatedata.spanclass = 'forbidden';
+                        templatedata.linkclass = 'unprohibitlink';
+                        templatedata.action = 'unprohibit';
+                        break;
+                    case 'prevent':
+                        row.find('a[data-role-id="' + roleid + '"]').first().closest('.allowed').remove();
+                        return;
+                    case 'unprohibit':
+                        row.find('a[data-role-id="' + roleid + '"]').first().closest('.forbidden').remove();
+                        return;
+                    default:
+                        return;
+                }
+                templates.render('core/permissionmanager_role',templatedata)
+                .done(function (content) {
+                    if (action == 'allow'){
+                        $(content).insertBefore(row.find('.allowmore:first'));
+                    }else if (action == 'prohibit'){
+                        $(content).insertBefore(row.find('.prohibitmore:first'));
+                        // Remove allowed link
+                        var allowedLink = row.find('.allowedroles').first().find('a[data-role-id="' + roleid + '"]');
+                        if (allowedLink) {
+                            allowedLink.first().closest('.allowed').remove();
+                        }
+                    }
+                    panel.hide();
+                })
+                .fail(notification.exception);
+            }
+            catch(err) {
+                notification.exception(err);
+            }
+        })
+        .fail(function(jqXHR, status, error) {
+            notification.exception(error);
+        });
+    };
+
+    /**
+     * Prompts user for selecting a role which is permitted
+     *
+     * @access private
+     * @method handleAddRole
+     * @param {event} e
+     */
+    var handleAddRole = function(e){
+        e.preventDefault();
+
+        $('body').one('rolesloaded', function() {
+            var link = $(e.currentTarget);
+            var action = link.data('action');
+            var row = link.closest('tr.rolecap');
+            var confirmationDetails = {
+                cap: row.data('humanname'),
+                context: contextname
+            };
+            var message = M.util.get_string('role' + action + 'info', 'core_role', confirmationDetails);
+            if (panel === null){
+                panel = new M.core.dialogue ({
+                    draggable: true,
+                    modal: true,
+                    closeButton: true,
+                    width: '450px'
+                });
+            }
+            panel.set('headerContent', M.util.get_string('role' + action + 'header', 'core_role'));
+
+            var i, existingrolelinks;
+
+            var roles = [];
+            switch (action){
+                case 'allow':
+                    existingrolelinks = row.find(SELECTORS.REMOVEROLE);
+                    break;
+                case 'prohibit':
+                    existingrolelinks = row.find(SELECTORS.UNPROHIBIT);
+                    break;
+            }
+            for (i in overideableroles) {
+                var disabled = '';
+                var disable = existingrolelinks.filter("[data-role-id='" + i + "']").length;
+                if (disable){
+                    disabled = 'disabled';
+                }
+                var roledetails = {roleid:i, rolename: overideableroles[i], disabled:disabled};
+                roles.push(roledetails);
+            }
+
+            templates.render('core/permissionmanager_panelcontent',{message:message, roles:roles})
+            .done(function (content) {
+                panel.set('bodyContent', content);
+                panel.show();
+                $('div.role_buttons').delegate('input', 'click',function(e){
+                    var roleid = $(e.currentTarget).data('role-id');
+                    changePermissions(row, roleid, action);
+                });
+            })
+            .fail(notification.exception);
+
+        });
+        loadOverideableRoles();
+    };
+
+    /**
+     * Prompts user when removing permission
+     *
+     * @access private
+     * @method handleRemoveRole
+     * @param {event} e
+     */
+    var handleRemoveRole = function(e){
+        e.preventDefault();
+        $('body').one('rolesloaded', function() {
+            var link = $(e.currentTarget);
+            var action = link.data('action');
+            var roleid = link.data('role-id');
+            var row = link.closest('tr.rolecap');
+            var questionDetails = {
+                role: overideableroles[roleid],
+                cap: row.data('humanname'),
+                context: contextname
+            };
+
+            notification.confirm(M.util.get_string('confirmunassigntitle', 'core_role'),
+                M.util.get_string('confirmrole' + action, 'core_role',questionDetails),
+                M.util.get_string('confirmunassignyes', 'core_role'),
+                M.util.get_string('confirmunassignno', 'core_role'),
+                function(){
+                   changePermissions(row, roleid, action);
+                }
+            );
+         });
+        loadOverideableRoles();
+    };
+
+    return /** @alias module:core/permissionmanager */ {
+        /**
+         * Initialize permissionmanager
+         * @access public
+         * @param {int} contextid
+         * @param {string} contextname
+         * @param {string} adminurl
+         */
+        initialize : function(args) {
+            contextid = args.contextid;
+            contextname = args.contextname;
+            adminurl = args.adminurl;
+            var body = $('body');
+            body.delegate(SELECTORS.ADDROLE, 'click', handleAddRole);
+            body.delegate(SELECTORS.REMOVEROLE, 'click', handleRemoveRole);
+        }
+    };
+});
\ No newline at end of file
diff --git a/lib/amd/src/tag.js b/lib/amd/src/tag.js
new file mode 100644 (file)
index 0000000..0edf6ef
--- /dev/null
@@ -0,0 +1,219 @@
+// 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/>.
+
+/**
+ * AJAX helper for the tag management page.
+ *
+ * @module     core/tag
+ * @package    core_tag
+ * @copyright  2015 Marina Glancy
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since      3.0
+ */
+define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str', 'core/config'],
+        function($, ajax, templates, notification, str, cfg) {
+    return /** @alias module:core/tag */ {
+
+        /**
+         * Initialises handlers for AJAX methods.
+         *
+         * @method init
+         */
+        init_manage_page: function() {
+
+            var update_modified = function(el) {
+                var row = el.closest('tr').get(0);
+                if (row) {
+                    var td = $(row).find('td.col-timemodified').get(0);
+                    str.get_string('now').done(function(s) {
+                        $(td).html(s);
+                    });
+                }
+            };
+
+            // Click handler for changing tag type.
+            $('.tag-management-table').delegate('.tagtype', 'click', function(e) {
+                e.preventDefault();
+                var target = $( this ),
+                    tagid = target.attr('data-id'),
+                    currentvalue = target.attr('data-value'),
+                    official = (currentvalue === "1") ? 0 : 1;
+
+                var promises = ajax.call([{
+                    methodname: 'core_tag_update_tags',
+                    args: { tags : [ { id : tagid , official : official } ] }
+                }, {
+                    methodname: 'core_tag_get_tags',
+                    args: { tags : [ { id : tagid } ] }
+                }], true);
+
+                $.when.apply($, promises)
+                    .done( function(updateresult, data) {
+                        if (updateresult.warnings[0] === undefined && data.tags[0] !== undefined) {
+                            templates.render('core_tag/tagtype', data.tags[0]).done(function(html) {
+                                update_modified(target);
+                                var parent = target.parent();
+                                target.replaceWith(html);
+                                parent.find('.tagtype').get(0).focus();
+                            });
+                        }
+                    });
+            });
+
+            // Click handler for flagging/resetting tag flag.
+            $('.tag-management-table').delegate('.tagflag', 'click', function(e) {
+                e.preventDefault();
+                var target = $( this ),
+                    tagid = target.attr('data-id'),
+                    currentvalue = target.attr('data-value'),
+                    flag = (currentvalue === "0") ? 1 : 0;
+
+                var promises = ajax.call([{
+                    methodname: 'core_tag_update_tags',
+                    args: { tags : [ { id : tagid , flag : flag } ] }
+                }, {
+                    methodname: 'core_tag_get_tags',
+                    args: { tags : [ { id : tagid } ] }
+                }], true);
+
+                $.when.apply($, promises)
+                    .done( function(updateresult, data) {
+                        if (updateresult.warnings[0] === undefined && data.tags[0] !== undefined) {
+                            var row = target.closest('tr').get(0);
+                            if (row) {
+                                if (data.tags[0].flag) {
+                                    $(row).addClass('flagged-tag');
+                                } else {
+                                    $(row).removeClass('flagged-tag');
+                                }
+                            }
+                            templates.render('core_tag/tagflag', data.tags[0]).done(function(html) {
+                                update_modified(target);
+                                var parent = target.parent();
+                                target.replaceWith(html);
+                                parent.find('.tagflag').get(0).focus();
+                            });
+                        }
+                    });
+            });
+
+            // Confirmation for single tag delete link.
+            $('.tag-management-table').delegate('a.tagdelete', 'click', function(e) {
+                e.preventDefault();
+                var href = $(this).attr('href');
+                str.get_strings([
+                        {key : 'delete'},
+                        {key : 'confirmdeletetag', component : 'tag'},
+                        {key : 'yes'},
+                        {key : 'no'},
+                    ]).done(function(s) {
+                        notification.confirm(s[0], s[1], s[2], s[3], function() {
+                            window.location.href = href;
+                        });
+                    }
+                );
+            });
+
+            // Confirmation for bulk tag delete button.
+            $("#tag-management-delete").click(function(e){
+                var form = $(this).closest('form').get(0),
+                    cnt = $(form).find("input[type=checkbox]:checked").length;
+                if (!cnt) {
+                    return false;
+                }
+                e.preventDefault();
+                str.get_strings([
+                        {key : 'delete'},
+                        {key : 'confirmdeletetags', component : 'tag'},
+                        {key : 'yes'},
+                        {key : 'no'},
+                    ]).done(function(s) {
+                        notification.confirm(s[0], s[1], s[2], s[3], function() {
+                            form.submit();
+                        });
+                    }
+                );
+            });
+
+            // Edit tag name.
+            $('.tag-management-table').delegate('.tagnameedit', 'click keypress', function(e) {
+                if (e.type === 'keypress' && e.keyCode !== 13) {
+                    return;
+                }
+                e.stopImmediatePropagation();
+                e.preventDefault();
+                var target = $(this),
+                    tdelement = $( target.closest('td').get(0) ),
+                    inputelement = $( tdelement.find('input').get(0) ),
+                    tagid = target.attr('data-id');
+
+                var change_name = function(tagid, newname) {
+                    var promises = ajax.call([{
+                        methodname: 'core_tag_update_tags',
+                        args: { tags : [ { id : tagid , rawname : newname } ] }
+                    }, {
+                        methodname: 'core_tag_get_tags',
+                        args: { tags : [ { id : tagid } ] }
+                    }], true);
+
+                    $.when.apply($, promises)
+                        .done( function(updateresult, data) {
+                            if (updateresult.warnings[0] !== undefined) {
+                                str.get_string('error').done(function(s) {
+                                    notification.alert(s, updateresult.warnings[0].message);
+                                });
+                            } else if (data.tags[0] !== undefined) {
+                                templates.render('core_tag/tagname', data.tags[0]).done(function(html) {
+                                    update_modified(tdelement);
+                                    tdelement.html(html);
+                                    $(tdelement.find('.tagnameedit').get(0)).focus();
+                                });
+                            }
+                        });
+                };
+
+                var turn_editing_off = function() {
+                    $('.tag-management-table td.tageditingon').each(function() {
+                        var td = $( this ),
+                            input = $( td.find('input').get(0) );
+                        input.off();
+                        td.removeClass('tageditingon');
+                        // Reset input value to the one that was there before editing.
+                        input.val(td.attr('data-value'));
+                    });
+                };
+
+                // Turn editing on for the current element and register handler for Enter/Esc keys.
+                turn_editing_off();
+                tdelement.addClass('tageditingon');
+                tdelement.attr('data-value', inputelement.val());
+                inputelement.select();
+                inputelement.on('keypress focusout', function(e) {
+                    if (cfg.behatsiterunning && e.type === 'focusout') {
+                        // Behat triggers focusout too often.
+                        return;
+                    }
+                    if (e.type === 'keypress' && e.keyCode === 13) {
+                        change_name(tagid, inputelement.val());
+                        turn_editing_off();
+                    }
+                    if ((e.type === 'keypress' && e.keyCode === 27) || e.type === 'focusout') {
+                        turn_editing_off();
+                    }
+                });
+            });
+        }
+    };
+});
\ No newline at end of file
index d03738d..93ed7d1 100644 (file)
@@ -298,6 +298,7 @@ define([ 'core/mustache',
 
         if (cached) {
             deferred.resolve(cached);
+            templateCache[searchKey] = cached;
             return deferred.promise();
         }
 
index d97ce53..aee4b4a 100644 (file)
@@ -95,6 +95,20 @@ class behat_form_field {
         return $instance->get_value();
     }
 
+    /**
+     * Presses specific keyboard key.
+     *
+     * @param mixed  $char     could be either char ('b') or char-code (98)
+     * @param string $modifier keyboard modifier (could be 'ctrl', 'alt', 'shift' or 'meta')
+     */
+    public function key_press($char, $modifier = null) {
+        // We delegate to the best guess, if we arrived here
+        // using the generic behat_form_field is because we are
+        // dealing with a fgroup element.
+        $instance = $this->guess_type();
+        return $instance->field->keyPress($char, $modifier);
+    }
+
     /**
      * Generic match implementation
      *
index 5d1874e..4866dc2 100644 (file)
@@ -1092,24 +1092,39 @@ class block_manager {
             $controls[] = new action_menu_link_secondary($url, $icon, $str, $attributes);
         }
 
-        // Assign roles icon.
-        if ($this->page->pagetype != 'my-index' && has_capability('moodle/role:assign', $block->context)) {
-            //TODO: please note it is sloppy to pass urls through page parameters!!
-            //      it is shortened because some web servers (e.g. IIS by default) give
-            //      a 'security' error if you try to pass a full URL as a GET parameter in another URL.
-            $return = $this->page->url->out(false);
-            $return = str_replace($CFG->wwwroot . '/', '', $return);
-
-            $rolesurl = new moodle_url('/admin/roles/assign.php', array('contextid'=>$block->context->id,
-                                                                         'returnurl'=>$return));
-            // Delete icon.
-            $str = new lang_string('assignrolesinblock', 'block', $blocktitle);
-            $controls[] = new action_menu_link_secondary(
-                $rolesurl,
-                new pix_icon('t/assignroles', $str, 'moodle', array('class' => 'iconsmall', 'title' => '')),
-                $str,
-                array('class' => 'editing_roles')
-            );
+        // Display either "Assign roles" or "Permissions" or "Change permissions" icon (whichever first is available).
+        if ($this->page->pagetype != 'my-index') {
+            $rolesurl = null;
+
+            if (get_assignable_roles($block->context, ROLENAME_SHORT)) {
+                $rolesurl = new moodle_url('/admin/roles/assign.php', array('contextid' => $block->context->id));
+                $str = new lang_string('assignrolesinblock', 'block', $blocktitle);
+                $icon = 'i/assignroles';
+            } else if (has_capability('moodle/role:review', $block->context) or get_overridable_roles($block->context)) {
+                $rolesurl = new moodle_url('/admin/roles/permissions.php', array('contextid' => $block->context->id));
+                $str = get_string('permissions', 'role');
+                $icon = 'i/permissions';
+            } else if (has_any_capability(array('moodle/role:assign', 'moodle/role:safeoverride', 'moodle/role:override', 'moodle/role:assign'), $block->context)) {
+                $rolesurl = new moodle_url('/admin/roles/check.php', array('contextid' => $block->context->id));
+                $str = get_string('checkpermissions', 'role');
+                $icon = 'i/checkpermissions';
+            }
+
+            if ($rolesurl) {
+                //TODO: please note it is sloppy to pass urls through page parameters!!
+                //      it is shortened because some web servers (e.g. IIS by default) give
+                //      a 'security' error if you try to pass a full URL as a GET parameter in another URL.
+                $return = $this->page->url->out(false);
+                $return = str_replace($CFG->wwwroot . '/', '', $return);
+                $rolesurl->param('returnurl', $return);
+
+                $controls[] = new action_menu_link_secondary(
+                    $rolesurl,
+                    new pix_icon($icon, $str, 'moodle', array('class' => 'iconsmall', 'title' => '')),
+                    $str,
+                    array('class' => 'editing_roles')
+                );
+            }
         }
 
         if ($this->user_can_delete_block($block)) {
index 99d17f4..ddd18d4 100644 (file)
@@ -288,15 +288,30 @@ class core_plugin_manager {
         foreach ($plugintypes as $type => $typedir) {
             $plugs = core_component::get_plugin_list($type);
             foreach ($plugs as $plug => $fullplug) {
+                $module = new stdClass();
                 $plugin = new stdClass();
                 $plugin->version = null;
-                $module = $plugin;
                 include($fullplug.'/version.php');
+
+                // Check if the legacy $module syntax is still used.
+                if (!is_object($module) or (count((array)$module) > 0)) {
+                    debugging('Unsupported $module syntax detected in version.php of the '.$type.'_'.$plug.' plugin.');
+                    $skipcache = true;
+                }
+
+                // Check if the component is properly declared.
+                if (empty($plugin->component) or ($plugin->component !== $type.'_'.$plug)) {
+                    debugging('Plugin '.$type.'_'.$plug.' does not declare valid $plugin->component in its version.php.');
+                    $skipcache = true;
+                }
+
                 $this->presentplugins[$type][$plug] = $plugin;
             }
         }
 
-        $cache->set('present', $this->presentplugins);
+        if (empty($skipcache)) {
+            $cache->set('present', $this->presentplugins);
+        }
     }
 
     /**
index b1a5e16..d565a22 100644 (file)
@@ -377,7 +377,7 @@ class manager {
         $user = null;
 
         if (!empty($CFG->opentogoogle)) {
-            if (is_web_crawler()) {
+            if (\core_useragent::is_web_crawler()) {
                 $user = guest_user();
             }
             $referer = get_local_referer(false);
similarity index 77%
rename from lib/classes/task/completion_cron_task.php
rename to lib/classes/task/completion_daily_task.php
index b34860a..a6597d8 100644 (file)
 namespace core\task;
 
 /**
- * Simple task to run the completion cron.
+ * Simple task to run the daily completion cron.
+ * @copyright  2013 onwards Martin Dougiamas  http://dougiamas.com.
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later.
  */
-class completion_cron_task extends scheduled_task {
+class completion_daily_task extends scheduled_task {
 
     /**
      * Get a descriptive name for this task (shown to admins).
@@ -34,7 +36,7 @@ class completion_cron_task extends scheduled_task {
      * @return string
      */
     public function get_name() {
-        return get_string('taskcompletioncron', 'admin');
+        return get_string('taskcompletiondaily', 'admin');
     }
 
     /**
@@ -45,9 +47,9 @@ class completion_cron_task extends scheduled_task {
         global $CFG;
 
         if ($CFG->enablecompletion) {
-            // Completion cron.
+            // Daily Completion cron.
             require_once($CFG->dirroot.'/completion/cron.php');
-            completion_cron();
+            completion_cron_mark_started();
         }
     }
 
diff --git a/lib/classes/task/completion_regular_task.php b/lib/classes/task/completion_regular_task.php
new file mode 100644 (file)
index 0000000..19be43f
--- /dev/null
@@ -0,0 +1,57 @@
+<?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/>.
+
+/**
+ * A scheduled task.
+ *
+ * @package    core
+ * @copyright  2015 Josh Willcock
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace core\task;
+
+/**
+ * Simple task to run the regular completion cron.
+ * @copyright  2015 Josh Willcock
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later.
+ */
+class completion_regular_task extends scheduled_task {
+
+    /**
+     * Get a descriptive name for this task (shown to admins).
+     *
+     * @return string
+     */
+    public function get_name() {
+        return get_string('taskcompletionregular', 'admin');
+    }
+
+    /**
+     * Do the job.
+     * Throw exceptions on errors (the job will be retried).
+     */
+    public function execute() {
+        global $CFG;
+
+        if ($CFG->enablecompletion) {
+            // Regular Completion cron.
+            require_once($CFG->dirroot.'/completion/cron.php');
+            completion_cron_criteria();
+            completion_cron_completions();
+        }
+    }
+
+}
index 29f1a07..fcc3c6d 100644 (file)
@@ -71,7 +71,7 @@ class core_useragent {
         self::DEVICETYPE_DEFAULT,
         self::DEVICETYPE_LEGACY,
         self::DEVICETYPE_MOBILE,
-        self::DEVICETYPE_TABLET
+        self::DEVICETYPE_TABLET,
     );
 
     /**
@@ -201,6 +201,7 @@ class core_useragent {
 
     /**
      * Returns true if the user appears to be on a tablet.
+     *
      * @return int
      */
     protected function is_useragent_tablet() {
@@ -208,6 +209,16 @@ class core_useragent {
         return (preg_match($tabletregex, $this->useragent));
     }
 
+    /**
+     * Whether the user agent relates to a web crawler.
+     * This includes all types of web crawler.
+     * @return bool
+     */
+    protected function is_useragent_web_crawler() {
+        $regex = '/Googlebot|google\.com|Yahoo! Slurp|\[ZSEBOT\]|msnbot|bingbot|BingPreview|Yandex|AltaVista|Baiduspider|Teoma/';
+        return (preg_match($regex, $this->useragent));
+    }
+
     /**
      * Gets a list of known device types.
      *
@@ -926,4 +937,15 @@ class core_useragent {
         // This browser does not support json.
         return false;
     }
+
+    /**
+     * Returns true if the client appears to be some kind of web crawler.
+     * This may include other types of crawler.
+     *
+     * @return bool
+     */
+    public static function is_web_crawler() {
+        $instance = self::instance();
+        return (bool) $instance->is_useragent_web_crawler();
+    }
 }
index 5db8ba8..e6e6b46 100644 (file)
@@ -91,6 +91,14 @@ function cron_run() {
                 mtrace("... used " . (microtime(1) - $pretime) . " seconds");
             }
             mtrace("Scheduled task failed: " . $task->get_name() . "," . $e->getMessage());
+            if ($CFG->debugdeveloper) {
+                 if (!empty($e->debuginfo)) {
+                    mtrace("Debug info:");
+                    mtrace($e->debuginfo);
+                }
+                mtrace("Backtrace:");
+                mtrace(format_backtrace($e->getTrace(), true));
+            }
             \core\task\manager::scheduled_task_failed($task);
         }
         get_mailer('close');
@@ -127,6 +135,14 @@ function cron_run() {
                 mtrace("... used " . (microtime(1) - $pretime) . " seconds");
             }
             mtrace("Adhoc task failed: " . get_class($task) . "," . $e->getMessage());
+            if ($CFG->debugdeveloper) {
+                 if (!empty($e->debuginfo)) {
+                    mtrace("Debug info:");
+                    mtrace($e->debuginfo);
+                }
+                mtrace("Backtrace:");
+                mtrace(format_backtrace($e->getTrace(), true));
+            }
             \core\task\manager::adhoc_task_failed($task);
         }
         get_mailer('close');
index 73636bd..7dac929 100644 (file)
@@ -77,6 +77,17 @@ $capabilities = array(
         )
     ),
 
+    'moodle/site:deleteanymessage' => array(
+
+        'riskbitmask' => RISK_DATALOSS,
+
+        'captype' => 'write',
+        'contextlevel' => CONTEXT_SYSTEM,
+        'archetypes' => array(
+            'manager' => CAP_ALLOW
+        )
+    ),
+
     'moodle/site:sendmessage' => array(
 
         'riskbitmask' => RISK_SPAM,
@@ -89,6 +100,15 @@ $capabilities = array(
         )
     ),
 
+    'moodle/site:deleteownmessage' => array(
+
+        'captype' => 'write',
+        'contextlevel' => CONTEXT_SYSTEM,
+        'archetypes' => array(
+            'user' => CAP_ALLOW
+        )
+    ),
+
     'moodle/site:approvecourse' => array(
 
         'riskbitmask' => RISK_XSS,
@@ -1070,6 +1090,17 @@ $capabilities = array(
         )
     ),
 
+    'moodle/course:tag' => array(
+        'riskbitmask' => RISK_SPAM,
+        'captype' => 'write',
+        'contextlevel' => CONTEXT_COURSE,
+        'archetypes' => array(
+            'manager' => CAP_ALLOW,
+            'editingteacher' => CAP_ALLOW,
+        ),
+        'clonepermissionsfrom' => 'moodle/course:update'
+    ),
+
     'moodle/blog:view' => array(
 
         'captype' => 'read',
@@ -1677,23 +1708,10 @@ $capabilities = array(
         'captype' => 'write',
         'contextlevel' => CONTEXT_SYSTEM,
         'archetypes' => array(
-            'teacher' => CAP_ALLOW,
-            'editingteacher' => CAP_ALLOW,
             'manager' => CAP_ALLOW
         )
     ),
 
-    'moodle/tag:create' => array(
-        'riskbitmask' => RISK_SPAM,
-
-        'captype' => 'write',
-        'contextlevel' => CONTEXT_SYSTEM,
-        'archetypes' => array(
-            'manager' => CAP_ALLOW,
-            'user' => CAP_ALLOW
-        )
-    ),
-
     'moodle/tag:edit' => array(
         'riskbitmask' => RISK_SPAM,
 
@@ -1710,7 +1728,6 @@ $capabilities = array(
         'captype' => 'write',
         'contextlevel' => CONTEXT_SYSTEM,
         'archetypes' => array(
-            'manager' => CAP_ALLOW,
             'user' => CAP_ALLOW
         )
     ),
index fcc226d..837a755 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8" ?>
-<XMLDB PATH="lib/db" VERSION="20150608" COMMENT="XMLDB file for core Moodle tables"
+<XMLDB PATH="lib/db" VERSION="20150824" COMMENT="XMLDB file for core Moodle tables"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:noNamespaceSchemaLocation="../../lib/xmldb/xmldb.xsd"
 >
         <FIELD NAME="contexturl" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="If this message is a notification of an event contexturl should contain a link to view this event. For example if its a notification of a forum post contexturl should contain a link to the forum post."/>
         <FIELD NAME="contexturlname" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="Display text for the contexturl"/>
         <FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
+        <FIELD NAME="timeuserfromdeleted" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
+        <FIELD NAME="timeusertodeleted" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
       </FIELDS>
       <KEYS>
         <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
       </KEYS>
       <INDEXES>
         <INDEX NAME="useridto" UNIQUE="false" FIELDS="useridto"/>
-        <INDEX NAME="useridfromto" UNIQUE="false" FIELDS="useridfrom, useridto"/>
+        <INDEX NAME="useridfromtodeleted" UNIQUE="false" FIELDS="useridfrom, useridto, timeuserfromdeleted, timeusertodeleted"/>
       </INDEXES>
     </TABLE>
     <TABLE NAME="message_read" COMMENT="Stores all messages that have been read">
         <FIELD NAME="contexturlname" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT=&qu