Merge branch 'MDL-55000-master' of git://github.com/andrewnicols/moodle
authorEloy Lafuente (stronk7) <stronk7@moodle.org>
Tue, 5 Jul 2016 17:35:56 +0000 (19:35 +0200)
committerEloy Lafuente (stronk7) <stronk7@moodle.org>
Tue, 5 Jul 2016 17:35:56 +0000 (19:35 +0200)
325 files changed:
.eslintrc
.jshintrc
.travis.yml
Gruntfile.js
admin/cli/install.php
admin/cli/install_database.php
admin/environment.xml
admin/index.php
admin/roles/ajax.php
admin/settings/plugins.php
admin/tool/behat/cli/run.php
admin/tool/health/styles.css
admin/tool/lp/amd/build/competencies.min.js
admin/tool/lp/amd/build/tree.min.js
admin/tool/lp/amd/src/actionselector.js
admin/tool/lp/amd/src/competencies.js
admin/tool/lp/amd/src/competency_outcomes.js
admin/tool/lp/amd/src/competency_plan_navigation.js
admin/tool/lp/amd/src/competency_rule.js
admin/tool/lp/amd/src/competencyactions.js
admin/tool/lp/amd/src/competencydialogue.js
admin/tool/lp/amd/src/competencypicker.js
admin/tool/lp/amd/src/competencypicker_user_plans.js
admin/tool/lp/amd/src/competencyruleconfig.js
admin/tool/lp/amd/src/course_competency_settings.js
admin/tool/lp/amd/src/dialogue.js
admin/tool/lp/amd/src/dragdrop-reorder.js
admin/tool/lp/amd/src/event_base.js
admin/tool/lp/amd/src/evidence_delete.js
admin/tool/lp/amd/src/frameworkactions.js
admin/tool/lp/amd/src/frameworks_datasource.js
admin/tool/lp/amd/src/grade_dialogue.js
admin/tool/lp/amd/src/grade_user_competency_inline.js
admin/tool/lp/amd/src/menubar.js
admin/tool/lp/amd/src/parentcompetency_form.js
admin/tool/lp/amd/src/planactions.js
admin/tool/lp/amd/src/scaleconfig.js
admin/tool/lp/amd/src/scalevalues.js
admin/tool/lp/amd/src/templateactions.js
admin/tool/lp/amd/src/tree.js
admin/tool/lp/amd/src/user_competency_course_navigation.js
admin/tool/lp/amd/src/user_competency_info.js
admin/tool/lp/amd/src/user_competency_plan_popup.js
admin/tool/lp/amd/src/user_competency_workflow.js
admin/tool/lp/amd/src/user_evidence_actions.js
admin/tool/lp/classes/external/course_module_summary_exporter.php
admin/tool/lp/coursecompetencies.php
admin/tool/lp/lib.php
admin/tool/lp/styles.css
admin/tool/mobile/tests/externallib_test.php
admin/tool/monitor/classes/eventobservers.php
admin/tool/monitor/classes/subscription.php
admin/tool/monitor/classes/subscription_manager.php
admin/tool/monitor/classes/task/check_subscriptions.php [new file with mode: 0644]
admin/tool/monitor/db/install.xml
admin/tool/monitor/db/tasks.php
admin/tool/monitor/db/upgrade.php
admin/tool/monitor/lang/en/tool_monitor.php
admin/tool/monitor/tests/subscription_test.php [new file with mode: 0644]
admin/tool/monitor/tests/task_check_subscriptions_test.php [new file with mode: 0644]
admin/tool/monitor/version.php
admin/tool/templatelibrary/amd/build/display.min.js
admin/tool/templatelibrary/amd/src/display.js
admin/tool/templatelibrary/amd/src/search.js
admin/tool/xmldb/actions/edit_field/edit_field.class.php
admin/tool/xmldb/actions/edit_index/edit_index.class.php
admin/tool/xmldb/actions/edit_key/edit_key.class.php
admin/tool/xmldb/actions/edit_table/edit_table.class.php
blocks/community/styles.css
blocks/navigation/amd/src/ajax_response_renderer.js
blocks/navigation/amd/src/nav_loader.js
blocks/private_files/tests/behat/block_private_files_dashboard.feature
blocks/private_files/tests/behat/block_private_files_frontpage.feature
comment/lib.php
competency/classes/api.php
competency/classes/course_competency_settings.php
competency/classes/evidence.php
competency/tests/api_test.php
completion/criteria/completion_criteria_activity.php
composer.json
composer.lock
course/completion_form.php
course/externallib.php
course/format/README.txt
course/format/topics/tests/behat/edit_delete_sections.feature
course/format/upgrade.txt
course/renderer.php
course/tests/behat/behat_course.php
course/tests/behat/restrict_available_activities.feature
course/tests/externallib_test.php
course/upgrade.txt [new file with mode: 0644]
course/view.php
course/yui/build/moodle-course-modchooser/moodle-course-modchooser-debug.js
course/yui/build/moodle-course-modchooser/moodle-course-modchooser-min.js
course/yui/build/moodle-course-modchooser/moodle-course-modchooser.js
course/yui/src/modchooser/js/modchooser.js
enrol/lti/classes/helper.php
enrol/tests/behat/behat_enrol.php
filter/mediaplugin/styles.css
grade/grading/form/guide/amd/src/comment_chooser.js
grade/grading/form/guide/lang/en/gradingform_guide.php
grade/grading/form/guide/tests/behat/behat_gradingform_guide.php
grade/grading/form/guide/tests/behat/edit_guide.feature
grade/lib.php
grade/report/grader/styles.css
grade/report/grader/tests/behat/ajax_grader.feature
grade/report/grader/yui/build/moodle-gradereport_grader-gradereporttable/moodle-gradereport_grader-gradereporttable-debug.js
grade/report/grader/yui/build/moodle-gradereport_grader-gradereporttable/moodle-gradereport_grader-gradereporttable-min.js
grade/report/grader/yui/build/moodle-gradereport_grader-gradereporttable/moodle-gradereport_grader-gradereporttable.js
grade/report/grader/yui/src/gradereporttable/js/floatingheaders.js
grade/report/singleview/styles.css
group/clientlib.js
install.php
install/lang/da_rum/langconfig.php [new file with mode: 0644]
install/lang/dz/admin.php
install/lang/dz/moodle.php
install/lang/pt/moodle.php
install/lang/ro/install.php
install/lang/ru/admin.php
install/lang/ru/moodle.php
install/lang/zh_tw/install.php
lang/en/admin.php
lang/en/auth.php
lang/en/backup.php
lang/en/badges.php
lang/en/deprecated.txt
lang/en/install.php
lang/en/moodle.php
lang/en/question.php
lang/en/search.php
lib/amd/build/form-autocomplete.min.js
lib/amd/build/permissionmanager.min.js
lib/amd/build/templates.min.js
lib/amd/src/ajax.js
lib/amd/src/event.js
lib/amd/src/first.js
lib/amd/src/form-autocomplete.js
lib/amd/src/fragment.js
lib/amd/src/localstorage.js
lib/amd/src/log.js
lib/amd/src/notification.js
lib/amd/src/permissionmanager.js
lib/amd/src/str.js
lib/amd/src/tag.js
lib/amd/src/templates.js
lib/amd/src/tree.js
lib/amd/src/url.js
lib/badgeslib.php
lib/behat/behat_base.php
lib/behat/form_field/behat_form_select.php
lib/classes/update/code_manager.php
lib/classes/user.php
lib/ddl/tests/ddl_test.php
lib/dml/pgsql_native_moodle_database.php
lib/dml/pgsql_native_moodle_recordset.php
lib/dml/tests/dml_test.php
lib/externallib.php
lib/filelib.php
lib/filestorage/file_storage.php
lib/form/form.js
lib/moodlelib.php
lib/navigationlib.php
lib/phpmailer/moodle_phpmailer.php
lib/setup.php
lib/tests/behat/behat_general.php
lib/tests/behat/behat_hooks.php
lib/tests/externallib_test.php
lib/tests/fixtures/update_validator/zips/multidir.zip [new file with mode: 0644]
lib/tests/moodlelib_test.php
lib/tests/text_test.php
lib/tests/update_code_manager_test.php
lib/upgrade.txt
lib/upgradelib.php
lib/xmldb/xmldb_table.php
login/tests/behat/behat_login.php [new file with mode: 0644]
login/tests/behat/change_password.feature [new file with mode: 0644]
message/output/airnotifier/message_output_airnotifier.php
mod/assign/amd/src/grading_actions.js
mod/assign/amd/src/grading_navigation.js
mod/assign/amd/src/grading_navigation_user_info.js
mod/assign/amd/src/grading_panel.js
mod/assign/db/services.php
mod/assign/externallib.php
mod/assign/feedback/editpdf/styles.css
mod/assign/feedback/editpdf/tests/behat/annotate_pdf.feature
mod/assign/feedback/editpdf/tests/behat/group_annotations.feature
mod/assign/lang/en/assign.php
mod/assign/locallib.php
mod/assign/submission/file/locallib.php
mod/assign/submission/file/tests/locallib_test.php [new file with mode: 0644]
mod/assign/submission/onlinetext/locallib.php
mod/assign/submission/onlinetext/tests/locallib_test.php [new file with mode: 0644]
mod/assign/submission_form.php
mod/assign/submissionplugin.php
mod/assign/tests/behat/quickgrading.feature
mod/assign/tests/externallib_test.php
mod/assign/tests/locallib_test.php
mod/assign/upgrade.txt
mod/assign/version.php
mod/book/classes/external.php
mod/book/tests/externallib_test.php
mod/book/tool/print/print.css
mod/chat/classes/external.php
mod/chat/gui_header_js/jsupdate.php
mod/chat/gui_header_js/jsupdated.php
mod/chat/tests/externallib_test.php
mod/choice/backup/moodle2/backup_choice_stepslib.php
mod/choice/classes/external.php
mod/choice/lang/en/choice.php
mod/choice/lib.php
mod/choice/mod_form.php
mod/choice/renderer.php
mod/choice/styles.css
mod/choice/tests/behat/allow_preview.feature
mod/choice/tests/behat/publish_results.feature
mod/data/classes/external.php
mod/data/lang/en/data.php
mod/data/mod_form.php
mod/data/preset/imagegallery/csstemplate.css
mod/data/tests/externallib_test.php
mod/feedback/classes/observer.php [new file with mode: 0644]
mod/feedback/db/events.php [moved from course/category.php with 57% similarity]
mod/feedback/lang/en/feedback.php
mod/feedback/lib.php
mod/feedback/mod_form.php
mod/feedback/tests/behat/behat_mod_feedback.php
mod/feedback/tests/events_test.php
mod/feedback/version.php
mod/forum/externallib.php
mod/forum/lang/en/forum.php
mod/forum/lib.php
mod/forum/styles.css
mod/forum/tests/behat/discussion_navigation.feature
mod/forum/tests/externallib_test.php
mod/glossary/classes/external.php
mod/imscp/classes/external.php
mod/lesson/lib.php
mod/lesson/mod_form.php
mod/lti/amd/src/cartridge_registration_form.js
mod/lti/amd/src/external_registration.js
mod/lti/amd/src/external_registration_return.js
mod/lti/amd/src/tool_card_controller.js
mod/lti/amd/src/tool_configure_controller.js
mod/lti/amd/src/tool_proxy.js
mod/lti/amd/src/tool_proxy_card_controller.js
mod/lti/amd/src/tool_type.js
mod/lti/classes/external.php
mod/lti/lang/en/lti.php
mod/lti/lib.php
mod/lti/locallib.php
mod/lti/tests/behat/addtool.feature
mod/lti/tests/behat/toolconfigure.feature
mod/lti/tests/externallib_test.php
mod/lti/typessettings.php
mod/lti/view.php
mod/quiz/amd/src/preflightcheck.js
mod/quiz/classes/external.php
mod/quiz/lib.php
mod/quiz/tests/behat/attempt_begin.feature
mod/quiz/tests/behat/editing_add.feature
mod/quiz/tests/external_test.php
mod/scorm/classes/external.php
mod/scorm/datamodels/scormlib.php
mod/scorm/lang/en/scorm.php
mod/scorm/mod_form.php
mod/scorm/styles.css
mod/scorm/tests/externallib_test.php
mod/survey/backup/moodle2/backup_survey_stepslib.php
mod/survey/classes/external.php
mod/survey/db/install.xml
mod/survey/db/upgrade.php
mod/survey/lang/en/survey.php
mod/survey/lib.php
mod/survey/mod_form.php
mod/survey/save.php
mod/survey/tests/behat/survey_completion.feature [new file with mode: 0644]
mod/survey/tests/externallib_test.php
mod/survey/version.php
mod/upgrade.txt
mod/wiki/classes/external.php
mod/wiki/tests/externallib_test.php
mod/workshop/renderer.php
my/lib.php
npm-shrinkwrap.json
package.json
question/type/multichoice/lang/en/qtype_multichoice.php
question/type/multichoice/renderer.php
question/type/multichoice/tests/behat/preview.feature
report/competency/amd/src/grading_popup.js
report/competency/amd/src/user_course_navigation.js
report/eventlist/tests/behat/mainsection.feature
report/upgrade.txt
repository/filepicker.php
repository/filesystem/lib.php
repository/lib.php
repository/repository_ajax.php
repository/upgrade.txt
search/classes/document.php
search/classes/manager.php
search/classes/output/form/search.php
search/tests/fixtures/mock_search_area.php
search/tests/fixtures/testable_core_search.php
search/tests/manager_test.php
theme/clean/classes/core_renderer.php
user/classes/course_form.php [new file with mode: 0644]
user/classes/search/user.php [new file with mode: 0644]
user/course.php [new file with mode: 0644]
user/edit.php
user/editadvanced.php
user/editadvanced_form.php
user/editlib.php
user/externallib.php
user/messageselect.php
user/tests/behat/course_preference.feature [new file with mode: 0644]
user/tests/externallib_test.php
user/tests/search_test.php [new file with mode: 0644]
version.php
webservice/lib.php
webservice/soap/locallib.php
webservice/tests/externallib_test.php
webservice/upgrade.txt
webservice/upload.php
webservice/xmlrpc/locallib.php
webservice/xmlrpc/tests/locallib_test.php [new file with mode: 0644]
webservice/xmlrpc/tests/xmlrpc_server_test.php [new file with mode: 0644]

index 31bd8ab..03dc85a 100644 (file)
--- a/.eslintrc
+++ b/.eslintrc
@@ -40,7 +40,7 @@
     'no-unreachable': 'warn',
     'no-unsafe-finally': 'error',
     'use-isnan': 'error',
-    'valid-jsdoc': ['warn', { 'requireReturn': false }],
+    'valid-jsdoc': ['warn', { 'requireReturn': false, 'requireParamDescription': false, 'requireReturnDescription': false }],
     'valid-typeof': 'error',
 
     // === Best Practices ===
     'max-params': 'off',
     'max-statements': 'off',
     'max-statements-per-line': 'off',
-    'new-cap': 'warn',
+    'new-cap': ['warn', { 'properties': false }],
     'new-parens': 'warn',
     'newline-after-var': 'off',
     'newline-before-return': 'off',
index ee94a05..b93ac60 100644 (file)
--- a/.jshintrc
+++ b/.jshintrc
@@ -1,3 +1,6 @@
+// NOTE: We use eslint now. This file is used only by shifter. We keep the configuration
+// here because shifter uses jshint after modules have been concating. Eslint can't
+// currently do this.
 {
     "asi":          false,
     "bitwise":      true,
index d1438f3..5a32a60 100644 (file)
@@ -14,15 +14,13 @@ language: php
 php:
     # We only run the highest and lowest supported versions to reduce the load on travis-ci.org.
     - 7.0
-    # - 5.6
-    # - 5.5
-    - 5.4
+    - 5.6
 
 env:
     # Although we want to run these jobs and see failures as quickly as possible, we also want to get the slowest job to
     # start first so that the total run time is not too high.
     #
-    # We only run MySQL on PHP 5.6, so run that first.
+    # We only run MySQL on PHP 7.0, so run that first.
     # CI Tests should be second-highest in priority as these only take <= 60 seconds to run under normal circumstances.
     # Postgres is significantly is pretty reasonable in its run-time.
 
@@ -50,17 +48,13 @@ matrix:
     exclude:
         # MySQL - it's just too slow.
         # Exclude it on all versions except for 7.0
-        # - env: DB=mysqli   TASK=PHPUNIT
-        #   php: 5.6
-        #
-        # - env: DB=mysqli   TASK=PHPUNIT
-        #   php: 5.5
 
         - env: DB=mysqli   TASK=PHPUNIT
-          php: 5.4
+          php: 5.6
 
+       # One grunt execution is enough.
         - env: DB=none     TASK=GRUNT
-          php: 5.4
+          php: 5.6
 
         # Moodle 2.7 is not compatible with PHP 7 for the upgrade test.
         - env: DB=pgsql    TASK=UPGRADE
index b728341..0b52cc8 100644 (file)
@@ -101,10 +101,6 @@ module.exports = function(grunt) {
 
     // Project configuration.
     grunt.initConfig({
-        jshint: {
-            options: {jshintrc: '.jshintrc'},
-            amd: { src: amdSrc }
-        },
         eslint: {
             // Even though warnings dont stop the build we don't display warnings by default because
             // at this moment we've got too many core warnings.
@@ -286,7 +282,8 @@ module.exports = function(grunt) {
     var changedFiles = Object.create(null);
     var onChange = grunt.util._.debounce(function() {
           var files = Object.keys(changedFiles);
-          grunt.config('jshint.amd.src', files);
+          grunt.config('eslint.amd.src', files);
+          grunt.config('eslint.yui.src', files);
           grunt.config('uglify.amd.files', [{ expand: true, src: files, rename: uglifyRename }]);
           grunt.config('shifter.options.paths', files);
           changedFiles = Object.create(null);
@@ -299,7 +296,6 @@ module.exports = function(grunt) {
 
     // Register NPM tasks.
     grunt.loadNpmTasks('grunt-contrib-uglify');
-    grunt.loadNpmTasks('grunt-contrib-jshint');
     grunt.loadNpmTasks('grunt-contrib-less');
     grunt.loadNpmTasks('grunt-contrib-watch');
     grunt.loadNpmTasks('grunt-eslint');
@@ -308,7 +304,7 @@ module.exports = function(grunt) {
     grunt.registerTask('shifter', 'Run Shifter against the current directory', tasks.shifter);
     grunt.registerTask('ignorefiles', 'Generate ignore files for linters', tasks.ignorefiles);
     grunt.registerTask('yui', ['eslint:yui', 'shifter']);
-    grunt.registerTask('amd', ['eslint:amd', 'jshint', 'uglify']);
+    grunt.registerTask('amd', ['eslint:amd', 'uglify']);
     grunt.registerTask('js', ['amd', 'yui']);
 
     // Register CSS taks.
index 9f3c0df..0b6c5ca 100644 (file)
@@ -147,10 +147,10 @@ define('PHPUNIT_TEST', false);
 define('IGNORE_COMPONENT_CACHE', true);
 
 // Check that PHP is of a sufficient version
-if (version_compare(phpversion(), "5.4.4") < 0) {
+if (version_compare(phpversion(), "5.6.5") < 0) {
     $phpversion = phpversion();
     // do NOT localise - lang strings would not work here and we CAN NOT move it after installib
-    fwrite(STDERR, "Moodle 2.7 or later requires at least PHP 5.4.4 (currently using version $phpversion).\n");
+    fwrite(STDERR, "Moodle 3.2 or later requires at least PHP 5.6.5 (currently using version $phpversion).\n");
     fwrite(STDERR, "Please upgrade your server software or install older Moodle version.\n");
     exit(1);
 }
index b66805e..f41263e 100644 (file)
@@ -63,10 +63,10 @@ Example:
 ";
 
 // Check that PHP is of a sufficient version
-if (version_compare(phpversion(), "5.4.4") < 0) {
+if (version_compare(phpversion(), "5.6.5") < 0) {
     $phpversion = phpversion();
     // do NOT localise - lang strings would not work here and we CAN NOT move it after installib
-    fwrite(STDERR, "Moodle 2.7 or later requires at least PHP 5.4.4 (currently using version $phpversion).\n");
+    fwrite(STDERR, "Moodle 3.2 or later requires at least PHP 5.6.5 (currently using version $phpversion).\n");
     fwrite(STDERR, "Please upgrade your server software or install older Moodle version.\n");
     exit(1);
 }
index f281b45..7bef8fc 100644 (file)
       </CUSTOM_CHECK>
     </CUSTOM_CHECKS>
   </MOODLE>
+  <MOODLE version="3.2" requires="2.7">
+    <UNICODE level="required">
+      <FEEDBACK>
+        <ON_ERROR message="unicoderequired" />
+      </FEEDBACK>
+    </UNICODE>
+    <DATABASE level="required">
+      <VENDOR name="mariadb" version="5.5.31" />
+      <VENDOR name="mysql" version="5.5.31" />
+      <VENDOR name="postgres" version="9.1" />
+      <VENDOR name="mssql" version="10.0" />
+      <VENDOR name="oracle" version="10.2" />
+    </DATABASE>
+    <PHP version="5.6.5" level="required">
+    </PHP>
+    <PCREUNICODE level="optional">
+      <FEEDBACK>
+        <ON_CHECK message="pcreunicodewarning" />
+      </FEEDBACK>
+    </PCREUNICODE>
+    <PHP_EXTENSIONS>
+      <PHP_EXTENSION name="iconv" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="iconvrequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="mbstring" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="mbstringrecommended" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="curl" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="curlrequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="openssl" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="opensslrecommended" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="tokenizer" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="tokenizerrecommended" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="xmlrpc" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="xmlrpcrecommended" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="soap" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="soaprecommended" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="ctype" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="ctyperequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="zip" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="ziprequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="zlib" level="required">
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="gd" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="gdrequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="simplexml" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="simplexmlrequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="spl" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="splrequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="pcre" level="required">
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="dom" level="required">
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="xml" level="required">
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="xmlreader" level="required">
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="intl" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="intlrecommended" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="json" level="required">
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="hash" level="required"/>
+    </PHP_EXTENSIONS>
+    <PHP_SETTINGS>
+      <PHP_SETTING name="memory_limit" value="96M" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="settingmemorylimit" />
+        </FEEDBACK>
+      </PHP_SETTING>
+      <PHP_SETTING name="file_uploads" value="1" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="settingfileuploads" />
+        </FEEDBACK>
+      </PHP_SETTING>
+      <PHP_SETTING name="opcache.enable" value="1" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="opcacherecommended" />
+        </FEEDBACK>
+      </PHP_SETTING>
+    </PHP_SETTINGS>
+    <CUSTOM_CHECKS>
+      <CUSTOM_CHECK file="lib/upgradelib.php" function="check_database_storage_engine" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="unsupporteddbstorageengine" />
+        </FEEDBACK>
+      </CUSTOM_CHECK>
+      <CUSTOM_CHECK file="question/engine/upgrade/upgradelib.php" function="quiz_attempts_upgraded" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="quizattemptsupgradedmessage" />
+        </FEEDBACK>
+      </CUSTOM_CHECK>
+      <CUSTOM_CHECK file="lib/upgradelib.php" function="check_slasharguments" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="slashargumentswarning" />
+        </FEEDBACK>
+      </CUSTOM_CHECK>
+      <CUSTOM_CHECK file="lib/upgradelib.php" function="check_database_tables_row_format" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="unsupporteddbtablerowformat" />
+        </FEEDBACK>
+      </CUSTOM_CHECK>
+      <CUSTOM_CHECK file="lib/upgradelib.php" function="check_unoconv_version" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="unoconvwarning" />
+        </FEEDBACK>
+      </CUSTOM_CHECK>
+    </CUSTOM_CHECKS>
+  </MOODLE>
 </COMPATIBILITY_MATRIX>
index 1000f93..382c252 100644 (file)
@@ -30,10 +30,10 @@ if (!file_exists('../config.php')) {
 }
 
 // Check that PHP is of a sufficient version as soon as possible
-if (version_compare(phpversion(), '5.4.4') < 0) {
+if (version_compare(phpversion(), '5.6.5') < 0) {
     $phpversion = phpversion();
     // do NOT localise - lang strings would not work here and we CAN NOT move it to later place
-    echo "Moodle 2.7 or later requires at least PHP 5.4.4 (currently using version $phpversion).<br />";
+    echo "Moodle 3.2 or later requires at least PHP 5.6.5 (currently using version $phpversion).<br />";
     echo "Please upgrade your server software or install older Moodle version.";
     die();
 }
index 717b8f2..2b66c9f 100644 (file)
@@ -36,6 +36,8 @@ require_login($course, false, $cm);
 require_capability('moodle/role:review', $context);
 require_sesskey();
 
+$OUTPUT->header();
+
 list($overridableroles, $overridecounts, $nameswithcounts) = get_overridable_roles($context,
         ROLENAME_BOTH, true);
 
index 1beaca5..680bd3d 100644 (file)
@@ -40,7 +40,9 @@ if ($hassiteconfig) {
         get_string('requiremodintro', 'admin'), get_string('requiremodintro_desc', 'admin'), 0));
     $ADMIN->add('modsettings', $temp);
 
-    foreach (core_plugin_manager::instance()->get_plugins_of_type('mod') as $plugin) {
+    $plugins = core_plugin_manager::instance()->get_plugins_of_type('mod');
+    core_collator::asort_objects_by_property($plugins, 'displayname');
+    foreach ($plugins as $plugin) {
         /** @var \core\plugininfo\mod $plugin */
         $plugin->load_settings($ADMIN, 'modsettings', $hassiteconfig);
     }
@@ -50,7 +52,9 @@ if ($hassiteconfig) {
     $temp = new admin_settingpage('manageformats', new lang_string('manageformats', 'core_admin'));
     $temp->add(new admin_setting_manageformats());
     $ADMIN->add('formatsettings', $temp);
-    foreach (core_plugin_manager::instance()->get_plugins_of_type('format') as $plugin) {
+    $plugins = core_plugin_manager::instance()->get_plugins_of_type('format');
+    core_collator::asort_objects_by_property($plugins, 'displayname');
+    foreach ($plugins as $plugin) {
         /** @var \core\plugininfo\format $plugin */
         $plugin->load_settings($ADMIN, 'formatsettings', $hassiteconfig);
     }
@@ -58,7 +62,9 @@ if ($hassiteconfig) {
     // blocks
     $ADMIN->add('modules', new admin_category('blocksettings', new lang_string('blocks')));
     $ADMIN->add('blocksettings', new admin_page_manageblocks());
-    foreach (core_plugin_manager::instance()->get_plugins_of_type('block') as $plugin) {
+    $plugins = core_plugin_manager::instance()->get_plugins_of_type('block');
+    core_collator::asort_objects_by_property($plugins, 'displayname');
+    foreach ($plugins as $plugin) {
         /** @var \core\plugininfo\block $plugin */
         $plugin->load_settings($ADMIN, 'blocksettings', $hassiteconfig);
     }
@@ -67,7 +73,9 @@ if ($hassiteconfig) {
     $ADMIN->add('modules', new admin_category('messageoutputs', new lang_string('messageoutputs', 'message')));
     $ADMIN->add('messageoutputs', new admin_page_managemessageoutputs());
     $ADMIN->add('messageoutputs', new admin_page_defaultmessageoutputs());
-    foreach (core_plugin_manager::instance()->get_plugins_of_type('message') as $plugin) {
+    $plugins = core_plugin_manager::instance()->get_plugins_of_type('message');
+    core_collator::asort_objects_by_property($plugins, 'displayname');
+    foreach ($plugins as $plugin) {
         /** @var \core\plugininfo\message $plugin */
         $plugin->load_settings($ADMIN, 'messageoutputs', $hassiteconfig);
     }
@@ -108,7 +116,9 @@ if ($hassiteconfig) {
     $temp = new admin_externalpage('authtestsettings', get_string('testsettings', 'core_auth'), new moodle_url("/auth/test_settings.php"), 'moodle/site:config', true);
     $ADMIN->add('authsettings', $temp);
 
-    foreach (core_plugin_manager::instance()->get_plugins_of_type('auth') as $plugin) {
+    $plugins = core_plugin_manager::instance()->get_plugins_of_type('auth');
+    core_collator::asort_objects_by_property($plugins, 'displayname');
+    foreach ($plugins as $plugin) {
         /** @var \core\plugininfo\auth $plugin */
         $plugin->load_settings($ADMIN, 'authsettings', $hassiteconfig);
     }
@@ -122,7 +132,9 @@ if ($hassiteconfig) {
     $temp = new admin_externalpage('enroltestsettings', get_string('testsettings', 'core_enrol'), new moodle_url("/enrol/test_settings.php"), 'moodle/site:config', true);
     $ADMIN->add('enrolments', $temp);
 
-    foreach(core_plugin_manager::instance()->get_plugins_of_type('enrol') as $plugin) {
+    $plugins = core_plugin_manager::instance()->get_plugins_of_type('enrol');
+    core_collator::asort_objects_by_property($plugins, 'displayname');
+    foreach ($plugins as $plugin) {
         /** @var \core\plugininfo\enrol $plugin */
         $plugin->load_settings($ADMIN, 'enrolments', $hassiteconfig);
     }
@@ -133,7 +145,9 @@ if ($hassiteconfig) {
     $temp = new admin_settingpage('manageeditors', new lang_string('editorsettings', 'editor'));
     $temp->add(new admin_setting_manageeditors());
     $ADMIN->add('editorsettings', $temp);
-    foreach (core_plugin_manager::instance()->get_plugins_of_type('editor') as $plugin) {
+    $plugins = core_plugin_manager::instance()->get_plugins_of_type('editor');
+    core_collator::asort_objects_by_property($plugins, 'displayname');
+    foreach ($plugins as $plugin) {
         /** @var \core\plugininfo\editor $plugin */
         $plugin->load_settings($ADMIN, 'editorsettings', $hassiteconfig);
     }
@@ -143,7 +157,9 @@ if ($hassiteconfig) {
     $temp = new admin_settingpage('manageantiviruses', new lang_string('antivirussettings', 'antivirus'));
     $temp->add(new admin_setting_manageantiviruses());
     $ADMIN->add('antivirussettings', $temp);
-    foreach (core_plugin_manager::instance()->get_plugins_of_type('antivirus') as $plugin) {
+    $plugins = core_plugin_manager::instance()->get_plugins_of_type('antivirus');
+    core_collator::asort_objects_by_property($plugins, 'displayname');
+    foreach ($plugins as $plugin) {
         /* @var \core\plugininfo\antivirus $plugin */
         $plugin->load_settings($ADMIN, 'antivirussettings', $hassiteconfig);
     }
@@ -182,7 +198,9 @@ if ($hassiteconfig) {
     }
     $ADMIN->add('filtersettings', $temp);
 
-    foreach (core_plugin_manager::instance()->get_plugins_of_type('filter') as $plugin) {
+    $plugins = core_plugin_manager::instance()->get_plugins_of_type('filter');
+    core_collator::asort_objects_by_property($plugins, 'displayname');
+    foreach ($plugins as $plugin) {
         /** @var \core\plugininfo\filter $plugin */
         $plugin->load_settings($ADMIN, 'filtersettings', $hassiteconfig);
     }
@@ -283,7 +301,9 @@ if ($hassiteconfig) {
         new lang_string('createrepository', 'repository'), $url, 'moodle/site:config', true));
     $ADMIN->add('repositorysettings', new admin_externalpage('repositoryinstanceedit',
         new lang_string('editrepositoryinstance', 'repository'), $url, 'moodle/site:config', true));
-    foreach (core_plugin_manager::instance()->get_plugins_of_type('repository') as $plugin) {
+    $plugins = core_plugin_manager::instance()->get_plugins_of_type('repository');
+    core_collator::asort_objects_by_property($plugins, 'displayname');
+    foreach ($plugins as $plugin) {
         /** @var \core\plugininfo\repository $plugin */
         $plugin->load_settings($ADMIN, 'repositorysettings', $hassiteconfig);
     }
@@ -337,7 +357,9 @@ if ($hassiteconfig) {
                         'admin'), new lang_string('configenablewsdocumentation', 'admin', $wsdoclink), false));
     $ADMIN->add('webservicesettings', $temp);
     /// links to protocol pages
-    foreach (core_plugin_manager::instance()->get_plugins_of_type('webservice') as $plugin) {
+    $plugins = core_plugin_manager::instance()->get_plugins_of_type('webservice');
+    core_collator::asort_objects_by_property($plugins, 'displayname');
+    foreach ($plugins as $plugin) {
         /** @var \core\plugininfo\webservice $plugin */
         $plugin->load_settings($ADMIN, 'webservicesettings', $hassiteconfig);
     }
@@ -409,7 +431,9 @@ if ($hassiteconfig || has_capability('moodle/question:config', $systemcontext))
             get_string('responsehistory', 'question'), '', 0, $hiddenofvisible));
 
     // Settings for particular question types.
-    foreach (core_plugin_manager::instance()->get_plugins_of_type('qtype') as $plugin) {
+    $plugins = core_plugin_manager::instance()->get_plugins_of_type('qtype');
+    core_collator::asort_objects_by_property($plugins, 'displayname');
+    foreach ($plugins as $plugin) {
         /** @var \core\plugininfo\qtype $plugin */
         $plugin->load_settings($ADMIN, 'qtypesettings', $hassiteconfig);
     }
@@ -421,7 +445,9 @@ if ($hassiteconfig && !empty($CFG->enableplagiarism)) {
     $ADMIN->add('plagiarism', new admin_externalpage('manageplagiarismplugins', new lang_string('manageplagiarism', 'plagiarism'),
         $CFG->wwwroot . '/' . $CFG->admin . '/plagiarism.php'));
 
-    foreach (core_plugin_manager::instance()->get_plugins_of_type('plagiarism') as $plugin) {
+    $plugins = core_plugin_manager::instance()->get_plugins_of_type('plagiarism');
+    core_collator::asort_objects_by_property($plugins, 'displayname');
+    foreach ($plugins as $plugin) {
         /** @var \core\plugininfo\plagiarism $plugin */
         $plugin->load_settings($ADMIN, 'plagiarism', $hassiteconfig);
     }
@@ -445,6 +471,7 @@ if ($hassiteconfig) {
     }
     if (!empty($pages)) {
         $ADMIN->add('modules', new admin_category('coursereports', new lang_string('coursereports')));
+        core_collator::asort_objects_by_property($pages, 'visiblename');
         foreach ($pages as $page) {
             $ADMIN->add('coursereports', $page);
         }
@@ -468,6 +495,7 @@ foreach (core_component::get_plugin_list('report') as $report => $plugindir) {
 $ADMIN->add('modules', new admin_category('reportplugins', new lang_string('reports')));
 $ADMIN->add('reportplugins', new admin_externalpage('managereports', new lang_string('reportsmanage', 'admin'),
                                                     $CFG->wwwroot . '/' . $CFG->admin . '/reports.php'));
+core_collator::asort_objects_by_property($pages, 'visiblename');
 foreach ($pages as $page) {
     $ADMIN->add('reportplugins', $page);
 }
@@ -510,6 +538,7 @@ if ($hassiteconfig) {
     }
     $ADMIN->add('searchplugins', $temp);
 
+    core_collator::asort_objects_by_property($pages, 'visiblename');
     foreach ($pages as $page) {
         $ADMIN->add('searchplugins', $page);
     }
@@ -523,7 +552,9 @@ if ($hassiteconfig) {
 }
 
 // Now add various admin tools.
-foreach (core_plugin_manager::instance()->get_plugins_of_type('tool') as $plugin) {
+$plugins = core_plugin_manager::instance()->get_plugins_of_type('tool');
+core_collator::asort_objects_by_property($plugins, 'displayname');
+foreach ($plugins as $plugin) {
     /** @var \core\plugininfo\tool $plugin */
     $plugin->load_settings($ADMIN, null, $hassiteconfig);
 }
@@ -534,6 +565,7 @@ if ($hassiteconfig) {
     $ADMIN->add('cache', new admin_externalpage('cacheconfig', new lang_string('cacheconfig', 'cache'), $CFG->wwwroot .'/cache/admin.php'));
     $ADMIN->add('cache', new admin_externalpage('cachetestperformance', new lang_string('testperformance', 'cache'), $CFG->wwwroot . '/cache/testperformance.php'));
     $ADMIN->add('cache', new admin_category('cachestores', new lang_string('cachestores', 'cache')));
+    $ADMIN->locate('cachestores')->set_sorting(true);
     foreach (core_component::get_plugin_list('cachestore') as $plugin => $path) {
         $settingspath = $path.'/settings.php';
         if (file_exists($settingspath)) {
@@ -547,7 +579,9 @@ if ($hassiteconfig) {
 // Add Calendar type settings.
 if ($hassiteconfig) {
     $ADMIN->add('modules', new admin_category('calendartype', new lang_string('calendartypes', 'calendar')));
-    foreach (core_plugin_manager::instance()->get_plugins_of_type('calendartype') as $plugin) {
+    $plugins = core_plugin_manager::instance()->get_plugins_of_type('calendartype');
+    core_collator::asort_objects_by_property($plugins, 'displayname');
+    foreach ($plugins as $plugin) {
         /** @var \core\plugininfo\calendartype $plugin */
         $plugin->load_settings($ADMIN, 'calendartype', $hassiteconfig);
     }
@@ -562,7 +596,9 @@ if ($hassiteconfig) {
 
 // Extend settings for each local plugin. Note that their settings may be in any part of the
 // settings tree and may be visible not only for administrators.
-foreach (core_plugin_manager::instance()->get_plugins_of_type('local') as $plugin) {
+$plugins = core_plugin_manager::instance()->get_plugins_of_type('local');
+core_collator::asort_objects_by_property($plugins, 'displayname');
+foreach ($plugins as $plugin) {
     /** @var \core\plugininfo\local $plugin */
     $plugin->load_settings($ADMIN, null, $hassiteconfig);
 }
index 98fc305..f8a37e7 100644 (file)
@@ -101,6 +101,9 @@ if (empty($options['torun'])) {
 if (extension_loaded('pcntl')) {
     $disabled = explode(',', ini_get('disable_functions'));
     if (!in_array('pcntl_signal', $disabled)) {
+        // Handle interrupts on PHP7.
+        declare(ticks = 1);
+
         pcntl_signal(SIGTERM, "signal_handler");
         pcntl_signal(SIGINT, "signal_handler");
     }
index 7994a8c..6a625f6 100644 (file)
 .path-admin-tool-health dl.notice dd {
     background-color: #e5db36;
 }
-.path-admin-tool-health dt.solution,
-.path-admin-tool-health dd.solution,
+.path-admin-tool-health dl dt.solution,
+.path-admin-tool-health dl dd.solution,
 .path-admin-tool-health div#healthnoproblemsfound {
-    background-color: #5BB83E !important;
+    background-color: #5BB83E;
 }
 .path-admin-tool-health dl.healthissues dt,
 .path-admin-tool-health dl.healthissues dd {
index cf57df2..c14f771 100644 (file)
Binary files a/admin/tool/lp/amd/build/competencies.min.js and b/admin/tool/lp/amd/build/competencies.min.js differ
index 4cc8611..899e3ce 100644 (file)
Binary files a/admin/tool/lp/amd/build/tree.min.js and b/admin/tool/lp/amd/build/tree.min.js differ
index d36f06f..8b32a48 100644 (file)
@@ -138,6 +138,7 @@ define(['jquery',
      * Find a node in the dialogue.
      *
      * @param {String} selector
+     * @return {JQuery} The node
      * @method _find
      */
     ActionSelector.prototype._find = function(selector) {
@@ -170,8 +171,8 @@ define(['jquery',
         for (var i in self._actions) {
             choices.push(self._actions[i]);
         }
-        var content = {'message': self._message, 'choices' : choices,
-            'confirm' : self._confirm, 'cancel' : self._cancel};
+        var content = {'message': self._message, 'choices': choices,
+            'confirm': self._confirm, 'cancel': self._cancel};
 
         return Templates.render('tool_lp/action_selector', content);
     };
index 6e1973c..8b5ae2e 100644 (file)
@@ -35,6 +35,7 @@ define(['jquery',
      *
      * @param {Number} itemid
      * @param {String} itemtype
+     * @param {Number} pagectxid
      */
     var competencies = function(itemid, itemtype, pagectxid) {
         this.itemid = itemid;
@@ -106,7 +107,7 @@ define(['jquery',
                 }
             ]);
         } else {
-            return null;
+            return;
         }
 
         requests[0].fail(notification.exception);
@@ -276,7 +277,7 @@ define(['jquery',
                 { key: message, component: 'tool_lp', param: competency.shortname },
                 { key: 'confirm', component: 'moodle' },
                 { key: 'cancel', component: 'moodle' }
-            ]).done(function (strings) {
+            ]).done(function(strings) {
                 notification.confirm(
                     strings[0], // Confirm.
                     strings[1], // Unlink the competency X from the course?
@@ -300,7 +301,7 @@ define(['jquery',
 
         if (localthis.itemtype == 'course') {
             // Course completion rule handling.
-            $('[data-region="coursecompetenciespage"]').on('change', 'select[data-field="ruleoutcome"]', function(e){
+            $('[data-region="coursecompetenciespage"]').on('change', 'select[data-field="ruleoutcome"]', function(e) {
                 var requests = [];
                 var pagerender = 'tool_lp/course_competencies_page';
                 var pageregion = 'coursecompetenciespage';
index 1ff679e..36b2a68 100644 (file)
@@ -64,7 +64,7 @@ define(['jquery',
          * Get the string for an outcome.
          *
          * @param  {Number} id The outcome code.
-         * @return {Promise Resolved with the string.
+         * @return {Promise} Resolved with the string.
          * @method getString
          */
         getString: function(id) {
index 34a70c7..3a3e505 100644 (file)
@@ -26,11 +26,11 @@ define(['jquery'], function($) {
     /**
      * CompetencyPlanNavigation
      *
-     * @param {String} The selector of the competency element.
-     * @param {String} The base url for the page (no params).
-     * @param {Number} The user id
-     * @param {Number} The competency id
-     * @param {Number} The plan id
+     * @param {String} competencySelector The selector of the competency element.
+     * @param {String} baseUrl The base url for the page (no params).
+     * @param {Number} userId The user id
+     * @param {Number} competencyId The competency id
+     * @param {Number} planId The plan id
      */
     var CompetencyPlanNavigation = function(competencySelector, baseUrl, userId, competencyId, planId) {
         this._baseUrl = baseUrl;
index 35def4a..e631bc2 100644 (file)
@@ -71,6 +71,7 @@ define(['jquery'], function($) {
         return null;
     };
 
+    // eslint-disable-next-line valid-jsdoc
     /**
      * Return the type of the module.
      *
@@ -152,7 +153,7 @@ define(['jquery'], function($) {
      * Trigger an event.
      *
      * @param {String} type The type of event.
-     * @param {Object} The data to pass to the listeners.
+     * @param {Object} data The data to pass to the listeners.
      * @method _trigger
      * @protected
      */
index 21e084e..c9c37bc 100644 (file)
@@ -66,7 +66,7 @@ define(['jquery',
         var parent = $('[data-region="competencyactions"]').data('competency');
 
         var params = {
-            competencyframeworkid : treeModel.getCompetencyFrameworkId(),
+            competencyframeworkid: treeModel.getCompetencyFrameworkId(),
             pagecontextid: pageContextId
         };
 
@@ -86,7 +86,7 @@ define(['jquery',
                 { key: 'addingcompetencywillresetparentrule', component: 'tool_lp', param: parent.shortname },
                 { key: 'yes', component: 'core' },
                 { key: 'no', component: 'core' }
-            ]).done(function (strings) {
+            ]).done(function(strings) {
                 notification.confirm(
                     strings[0],
                     strings[1],
@@ -157,7 +157,7 @@ define(['jquery',
                 { key: confirmMessage, component: 'tool_lp' },
                 { key: 'yes', component: 'moodle' },
                 { key: 'no', component: 'moodle' }
-            ]).done(function (strings) {
+            ]).done(function(strings) {
                 notification.confirm(
                     strings[0], // Confirm.
                     strings[1], // Delete competency X?
@@ -187,8 +187,13 @@ define(['jquery',
         });
         treeRoot.show();
 
-        body.on('click', '[data-action="move"]', function() { popup.close(); confirmMove(); });
-        body.on('click', '[data-action="cancel"]', function() { popup.close(); });
+        body.on('click', '[data-action="move"]', function() {
+          popup.close();
+          confirmMove();
+        });
+        body.on('click', '[data-action="cancel"]', function() {
+          popup.close();
+        });
     };
 
     /**
@@ -213,6 +218,7 @@ define(['jquery',
 
     /**
      * A node was chosen and "Move" was selected from the menu. Open a popup to select the target.
+     * @param {Event} e
      * @method moveHandler
      */
     var moveHandler = function(e) {
@@ -230,7 +236,7 @@ define(['jquery',
                     competencyframeworkid: competency.competencyframeworkid,
                     searchtext: ''
                 }
-            },{
+            }, {
                 methodname: 'core_competency_read_competency_framework',
                 args: {
                     id: competency.competencyframeworkid
@@ -257,7 +263,7 @@ define(['jquery',
                 { key: 'movecompetency', component: 'tool_lp', param: competency.shortname },
                 { key: 'move', component: 'tool_lp' },
                 { key: 'cancel', component: 'moodle' }
-            ]).done(function (strings) {
+            ]).done(function(strings) {
 
                 var context = {
                     framework: framework,
@@ -288,8 +294,8 @@ define(['jquery',
         var competency = $('[data-region="competencyactions"]').data('competency');
 
         var params = {
-            competencyframeworkid : treeModel.getCompetencyFrameworkId(),
-            id : competency.id,
+            competencyframeworkid: treeModel.getCompetencyFrameworkId(),
+            id: competency.id,
             parentid: competency.parentid,
             pagecontextid: pageContextId
         };
@@ -300,6 +306,7 @@ define(['jquery',
 
     /**
      * Re-render the page with the latest data.
+     * @param {Object} context
      * @method reloadPage
      */
     var reloadPage = function(context) {
@@ -313,6 +320,7 @@ define(['jquery',
 
     /**
      * Perform a search and render the page with the new search results.
+     * @param {Event} e
      * @method updateSearchHandler
      */
     var updateSearchHandler = function(e) {
@@ -381,7 +389,7 @@ define(['jquery',
                 courses: courses
             };
             templates.render('tool_lp/linked_courses_summary', context).done(function(html) {
-                str.get_string('linkedcourses', 'tool_lp').done(function (linkedcourses) {
+                str.get_string('linkedcourses', 'tool_lp').done(function(linkedcourses) {
                     new Dialogue(
                         linkedcourses, // Title.
                         html, // The linked courses.
@@ -413,7 +421,7 @@ define(['jquery',
                     });
                 });
 
-                calls.push( {
+                calls.push({
                     methodname: 'tool_lp_data_for_related_competencies_section',
                     args: { competencyid: relatedTarget.id }
                 });
@@ -486,7 +494,7 @@ define(['jquery',
                 str.get_strings([
                 { key: 'competencycannotbedeleted', component: 'tool_lp', param: competency.shortname },
                 { key: 'cancel', component: 'moodle' }
-                ]).done(function (strings) {
+                ]).done(function(strings) {
                     notification.alert(
                         null,
                         strings[0]
@@ -514,7 +522,7 @@ define(['jquery',
             { key: confirmMessage, component: 'tool_lp', param: competency.shortname },
             { key: 'delete', component: 'moodle' },
             { key: 'cancel', component: 'moodle' }
-        ]).done(function (strings) {
+        ]).done(function(strings) {
             notification.confirm(
                 strings[0], // Confirm.
                 strings[1], // Delete competency X?
@@ -528,6 +536,7 @@ define(['jquery',
     /**
      * HTML5 implementation of drag/drop (there is an accesible alternative in the menus).
      * @method dragStart
+     * @param {Event} e
      */
     var dragStart = function(e) {
         e.originalEvent.dataTransfer.setData('text', $(e.target).parent().data('id'));
@@ -536,6 +545,7 @@ define(['jquery',
     /**
      * HTML5 implementation of drag/drop (there is an accesible alternative in the menus).
      * @method allowDrop
+     * @param {Event} e
      */
     var allowDrop = function(e) {
         e.originalEvent.dataTransfer.dropEffect = 'move';
@@ -545,6 +555,7 @@ define(['jquery',
     /**
      * HTML5 implementation of drag/drop (there is an accesible alternative in the menus).
      * @method dragEnter
+     * @param {Event} e
      */
     var dragEnter = function(e) {
         e.preventDefault();
@@ -554,6 +565,7 @@ define(['jquery',
     /**
      * HTML5 implementation of drag/drop (there is an accesible alternative in the menus).
      * @method dragLeave
+     * @param {Event} e
      */
     var dragLeave = function(e) {
         e.preventDefault();
@@ -563,6 +575,7 @@ define(['jquery',
     /**
      * HTML5 implementation of drag/drop (there is an accesible alternative in the menus).
      * @method dropOver
+     * @param {Event} e
      */
     var dropOver = function(e) {
         e.preventDefault();
@@ -614,7 +627,7 @@ define(['jquery',
     /**
      * Log the competency viewed event.
      *
-     * @param  {Object} The competency.
+     * @param  {Object} competency The competency.
      * @method triggerCompetencyViewedEvent
      */
     var triggerCompetencyViewedEvent = function(competency) {
@@ -729,6 +742,7 @@ define(['jquery',
      * @method selectionChanged
      * @param {Event} evt The event that triggered the selection change.
      * @param {Object} params The parameters for the event. Contains a list of selected nodes.
+     * @return {Boolean}
      */
     var selectionChanged = function(evt, params) {
         var node = params.selected,
@@ -781,7 +795,7 @@ define(['jquery',
      * Return the string "Selected <taxonomy>".
      *
      * @function parseTaxonomies
-     * @param  {String} Comma separated list of taxonomies.
+     * @param  {String} taxonomiesstr Comma separated list of taxonomies.
      * @return {Array} of level => taxonomystr
      */
     var parseTaxonomies = function(taxonomiesstr) {
index affcea6..9902eed 100644 (file)
@@ -43,12 +43,13 @@ define(['jquery',
      *
      */
     var Competencydialogue = function() {
+      // Intentionally left empty.
     };
 
     /**
      * Log the competency viewed event.
      *
-     * @param  {Number} The competency ID.
+     * @param  {Number} competencyId The competency ID.
      * @method triggerCompetencyViewedEvent
      */
     Competencydialogue.prototype.triggerCompetencyViewedEvent = function(competencyId) {
@@ -112,7 +113,7 @@ define(['jquery',
     /**
      * The action on the click event.
      *
-     * @param {Event} event click
+     * @param {Event} e event click
      * @method clickEventHandler
      */
     Competencydialogue.prototype.clickEventHandler = function(e) {
@@ -135,6 +136,7 @@ define(['jquery',
      * Get a promise on data competency.
      *
      * @param {Number} competencyid
+     * @param {Object} options
      * @return {Promise} return promise on data request
      * @method getCompetencyDataPromise
      */
@@ -160,8 +162,6 @@ define(['jquery',
          * Initialise the competency dialogue module.
          *
          * Only the first call matters.
-         *
-         * @return {Void}
          */
         init: function() {
             if (typeof instance !== 'undefined') {
index d6247fe..272d3e0 100644 (file)
@@ -231,7 +231,10 @@ define(['jquery',
                 competencyframeworkid: frameworkId
             }}
         ])[0].done(function(competencies) {
-
+          /**
+           * @param {Object} parent
+           * @param {Array} competencies
+           */
             function addCompetencyChildren(parent, competencies) {
                 for (var i = 0; i < competencies.length; i++) {
                     if (competencies[i].parentid == parent.id) {
@@ -265,6 +268,7 @@ define(['jquery',
      * Find a node in the dialogue.
      *
      * @param {String} selector
+     * @return {JQuery}
      * @method _find
      */
     Picker.prototype._find = function(selector) {
@@ -275,6 +279,7 @@ define(['jquery',
      * Convenience method to get a framework object.
      *
      * @param {Number} fid The framework ID.
+     * @return {Object}
      * @method _getFramework
      */
     Picker.prototype._getFramework = function(fid) {
@@ -437,7 +442,7 @@ define(['jquery',
      *
      * This needs to be set after reset/close.
      *
-     * @params {Number[]} The IDs.
+     * @param {Number[]} ids The IDs.
      * @method _setDisallowedCompetencyIDs
      */
     Picker.prototype.setDisallowedCompetencyIDs = function(ids) {
@@ -448,7 +453,7 @@ define(['jquery',
      * Trigger an event.
      *
      * @param {String} type The type of event.
-     * @param {Object} The data to pass to the listeners.
+     * @param {Object} data The data to pass to the listeners.
      * @method _reset
      */
     Picker.prototype._trigger = function(type, data) {
index 7291829..b417b67 100644 (file)
@@ -39,8 +39,8 @@ define(['jquery',
     /**
      * Competency picker in plan class.
      *
+     * @param {Number} userId
      * @param {Number|false} singlePlan The ID of the plan when limited to one.
-     * @param {String} pageContextIncludes One of 'children', 'parents', 'self'.
      * @param {Boolean} multiSelect Support multi-select in the tree.
      */
     var Picker = function(userId, singlePlan, multiSelect) {
@@ -88,7 +88,7 @@ define(['jquery',
      * @param {Number} planId The planId.
      * @param {String} searchText Limit the competencies to those matching the text.
      * @method _fetchCompetencies
-     * @return {Promise}
+     * @return {Promise} The promise object.
      */
     Picker.prototype._fetchCompetencies = function(planId, searchText) {
         var self = this;
index 4894d36..f207939 100644 (file)
@@ -70,7 +70,6 @@ define(['jquery',
      *
      * Triggered when a change occured.
      *
-     * @return {Void}
      * @method _afterChange
      * @protected
      */
@@ -87,9 +86,10 @@ define(['jquery',
      *
      * Triggered when a change occured in a specific rule config.
      *
-     * @return {Void}
      * @method _afterRuleConfigChange
      * @protected
+     * @param {Event} e
+     * @param {Rule} rule
      */
     RuleConfig.prototype._afterRuleConfigChange = function(e, rule) {
         if (rule != this._getRule()) {
@@ -102,7 +102,6 @@ define(['jquery',
     /**
      * After render hook.
      *
-     * @return {Promise}
      * @method _afterRender
      * @protected
      */
@@ -181,6 +180,7 @@ define(['jquery',
      * Find a node in the dialogue.
      *
      * @param {String} selector
+     * @return {JQuery}
      * @method _find
      * @protected
      */
@@ -444,7 +444,6 @@ define(['jquery',
     /**
      * Set up the instance.
      *
-     * @return {Void}
      * @method _setUp
      * @protected
      */
@@ -482,7 +481,6 @@ define(['jquery',
     /**
      * Called when the user switches outcome.
      *
-     * @return {Void}
      * @method _switchedOutcome
      * @protected
      */
@@ -507,7 +505,6 @@ define(['jquery',
     /**
      * Called when the user switches rule.
      *
-     * @return {Void}
      * @method _switchedRule
      * @protected
      */
@@ -535,7 +532,7 @@ define(['jquery',
      * Trigger an event.
      *
      * @param {String} type The type of event.
-     * @param {Object} The data to pass to the listeners.
+     * @param {Object} data The data to pass to the listeners.
      * @method _trigger
      * @protected
      */
index bff7349..1cc3933 100644 (file)
@@ -57,7 +57,7 @@ define(['jquery',
         e.preventDefault();
 
         templates.render('tool_lp/course_competency_settings', context).done(function(html) {
-            str.get_string('configurecoursecompetencysettings', 'tool_lp').done(function (title) {
+            str.get_string('configurecoursecompetencysettings', 'tool_lp').done(function(title) {
                 this._dialogue = new Dialogue(
                     title,
                     html,
index 882ee5b..04a570f 100644 (file)
@@ -43,7 +43,7 @@ define(['core/yui'], function(Y) {
             wide = false;
         }
 
-        Y.use('moodle-core-notification', 'timers', function () {
+        Y.use('moodle-core-notification', 'timers', function() {
             var width = '480px';
             if (wide) {
                 width = '800px';
@@ -92,6 +92,7 @@ define(['core/yui'], function(Y) {
 
     /**
      * Get content.
+     * @return {node}
      */
     dialogue.prototype.getContent = function() {
         return this.yuiDialogue.bodyNode.getDOMNode();
index d14c9dd..a94eb30 100644 (file)
@@ -70,8 +70,8 @@ define(['core/str', 'core/yui'], function(str, Y) {
                 { key: 'emptydragdropregion', component: 'moodle' },
                 { key: 'movecontent', component: 'moodle' },
                 { key: 'tocontent', component: 'moodle' },
-            ]).done( function () {
-                Y.use('moodle-tool_lp-dragdrop-reorder', function () {
+            ]).done(function() {
+                Y.use('moodle-tool_lp-dragdrop-reorder', function() {
 
                     var context = {
                         callback: callback
index 1bda7ed..fe8433c 100644 (file)
@@ -48,7 +48,7 @@ define(['jquery'], function($) {
      * Trigger an event.
      *
      * @param {String} type The type of event.
-     * @param {Object} The data to pass to the listeners.
+     * @param {Object} data The data to pass to the listeners.
      * @method _trigger
      */
     Base.prototype._trigger = function(type, data) {
index 19a5534..44698d1 100644 (file)
@@ -35,7 +35,6 @@ define(['jquery',
      *
      * @param {String} triggerSelector The node on which the click will happen.
      * @param {String} containerSelector The parent node that will be removed and contains the evidence ID.
-     * @return {Void}
      */
     var register = function(triggerSelector, containerSelector) {
         if (typeof selectors[triggerSelector] !== 'undefined') {
@@ -62,7 +61,7 @@ define(['jquery',
                 { key: 'areyousure', component: 'moodle' },
                 { key: 'delete', component: 'moodle' },
                 { key: 'cancel', component: 'moodle' }
-            ]).done(function (strings) {
+            ]).done(function(strings) {
                 Notification.confirm(
                     strings[0], // Confirm.
                     strings[1], // Are you sure?
index a783033..b554495 100644 (file)
@@ -93,17 +93,17 @@ define(['jquery', 'core/templates', 'core/ajax', 'core/notification', 'core/str'
                 }
             }
         }]);
-        requests[0].done(function (success) {
+        requests[0].done(function(success) {
             if (success === false) {
                 var req = ajax.call([{
                     methodname: 'core_competency_read_competency_framework',
                     args: { id: frameworkid }
                 }]);
-                req[0].done(function (framework) {
+                req[0].done(function(framework) {
                     str.get_strings([
                         { key: 'frameworkcannotbedeleted', component: 'tool_lp', param: framework.shortname },
                         { key: 'cancel', component: 'moodle' }
-                    ]).done(function (strings) {
+                    ]).done(function(strings) {
                         notification.alert(
                             null,
                             strings[0]
@@ -136,7 +136,7 @@ define(['jquery', 'core/templates', 'core/ajax', 'core/notification', 'core/str'
                 { key: 'deletecompetencyframework', component: 'tool_lp', param: framework.shortname },
                 { key: 'delete', component: 'moodle' },
                 { key: 'cancel', component: 'moodle' }
-            ]).done(function (strings) {
+            ]).done(function(strings) {
                 notification.confirm(
                     strings[0], // Confirm.
                     strings[1], // Delete competency framework X?
index a4c9274..3638830 100644 (file)
@@ -75,7 +75,6 @@ define(['jquery', 'core/ajax', 'core/notification'], function($, Ajax, Notificat
          * @param {String} selector The selector of the auto complete element.
          * @param {String} query The query string.
          * @param {Function} callback A callback function receiving an array of results.
-         * @return {Void}
          */
         transport: function(selector, query, callback) {
             var el = $(selector),
index 9063b50..576b511 100644 (file)
@@ -31,6 +31,7 @@ define(['jquery',
 
     /**
      * Grade dialogue class.
+     * @param {Array} ratingOptions
      */
     var Grade = function(ratingOptions) {
         EventBase.prototype.constructor.apply(this, []);
@@ -46,7 +47,6 @@ define(['jquery',
     /**
      * After render hook.
      *
-     * @return {Promise}
      * @method _afterRender
      * @protected
      */
@@ -118,6 +118,7 @@ define(['jquery',
      *
      * @param {String} selector
      * @method _find
+     * @returns {node} The node
      * @protected
      */
     Grade.prototype._find = function(selector) {
index fd9d6ac..3558b9f 100644 (file)
@@ -34,12 +34,12 @@ define(['jquery',
      * InlineEditor
      *
      * @param {String} selector The selector to trigger the grading.
-     * @param {Number} The id of the scale for this competency.
-     * @param {Number} The id of the competency.
-     * @param {Number} The id of the user.
-     * @param {Number} The id of the plan.
-     * @param {Number} The id of the course.
-     * @param {String} Language string for choose a rating.
+     * @param {Number} scaleId The id of the scale for this competency.
+     * @param {Number} competencyId The id of the competency.
+     * @param {Number} userId The id of the user.
+     * @param {Number} planId The id of the plan.
+     * @param {Number} courseId The id of the course.
+     * @param {String} chooseStr Language string for choose a rating.
      */
     var InlineEditor = function(selector, scaleId, competencyId, userId, planId, courseId, chooseStr) {
         EventBase.prototype.constructor.apply(this, []);
index a91d770..6889936 100644 (file)
@@ -44,7 +44,7 @@ define(['jquery'], function($) {
     /**
      * Constructor
      *
-     * @param {$} Jquery collection matching the root of the menu.
+     * @param {$} menuRoot Jquery collection matching the root of the menu.
      * @param {Function[]} handlers, called when a menu item is chosen.
      */
     var Menubar = function(menuRoot, handlers) {
@@ -144,7 +144,7 @@ define(['jquery'], function($) {
      * @method handleClick
      * @param {Object} item is the jquery object of the item firing the event
      * @param {Event} e is the associated event object
-     * @return boolean Returns false
+     * @return {boolean} Returns false
      */
     Menubar.prototype.handleClick = function(item, e) {
         e.stopPropagation();
@@ -166,7 +166,7 @@ define(['jquery'], function($) {
             this.activeItem = null;
 
             // Close the menu.
-            this.menuRoot.find('ul').not('.root-level').attr('aria-hidden','true');
+            this.menuRoot.find('ul').not('.root-level').attr('aria-hidden', 'true');
             // Follow any link, or call the click handlers.
             var anchor = item.find('a').first();
             var clickEvent = new $.Event('click');
@@ -290,7 +290,7 @@ define(['jquery'], function($) {
                 subMenuContainer.css('margin-right', '-' + marginright + 'px');
             }
         } else {
-            if ( pos.left + menuRealWidth > $(window).width()) {
+            if (pos.left + menuRealWidth > $(window).width()) {
                 marginleft = menuRealWidth - widthmenuRoot;
                 subMenuContainer.css('margin-left', '-' + marginleft + 'px');
             }
@@ -319,7 +319,7 @@ define(['jquery'], function($) {
             return true;
         }
 
-        switch(e.keyCode) {
+        switch (e.keyCode) {
             case this.keys.tab: {
 
                 // Hide all menu items and update their aria attributes.
@@ -438,7 +438,7 @@ define(['jquery'], function($) {
             // This is the root level move to next sibling. This will require closing
             // the current child menu and opening the new one.
 
-            if (menuIndex < menuNum-1) {
+            if (menuIndex < menuNum - 1) {
                 // Not the last root menu.
                 newItem = item.next();
             } else { // Wrap to first item.
@@ -501,7 +501,7 @@ define(['jquery'], function($) {
                 menuIndex = this.rootMenus.index(rootItem);
 
                 // If this is not the last root menu item, move to the next one.
-                if (menuIndex < this.rootMenus.length-1) {
+                if (menuIndex < this.rootMenus.length - 1) {
                     newItem = rootItem.next();
                 } else {
                     // Loop.
@@ -687,7 +687,7 @@ define(['jquery'], function($) {
         // to the next item with a title that begins with that character.
         if (startChr) {
             var match = false;
-            var curNdx = menuIndex+1;
+            var curNdx = menuIndex + 1;
 
             // Check if the active item was the last one on the list.
             if (curNdx == menuNum) {
@@ -696,7 +696,7 @@ define(['jquery'], function($) {
 
             // Iterate through the menu items (starting from the current item and wrapping) until a match is found
             // or the loop returns to the current menu item.
-            while (curNdx != menuIndex)  {
+            while (curNdx != menuIndex) {
 
                 var titleChr = menuItems.eq(curNdx).html().charAt(0);
 
@@ -705,7 +705,7 @@ define(['jquery'], function($) {
                     break;
                 }
 
-                curNdx = curNdx+1;
+                curNdx = curNdx + 1;
 
                 if (curNdx == menuNum) {
                     // Reached the end of the list, start again at the beginning.
@@ -724,8 +724,8 @@ define(['jquery'], function($) {
                 return item;
             }
         } else {
-            if (menuIndex < menuNum-1) {
-                newItem = menuItems.eq(menuIndex+1);
+            if (menuIndex < menuNum - 1) {
+                newItem = menuItems.eq(menuIndex + 1);
             } else {
                 newItem = menuItems.first();
             }
@@ -763,7 +763,7 @@ define(['jquery'], function($) {
 
         // If item is not the first item in its menu, move to the previous item.
         if (menuIndex > 0) {
-            newItem = menuItems.eq(menuIndex-1);
+            newItem = menuItems.eq(menuIndex - 1);
         } else {
             // Loop to top of menu.
             newItem = menuItems.last();
@@ -823,7 +823,7 @@ define(['jquery'], function($) {
          *                             { "[data-action='add']" : callAddFunction }
          */
         enhance: function(selector, handler) {
-            $(selector).each(function (index, element) {
+            $(selector).each(function(index, element) {
                 var menuRoot = $(element);
                 // Don't enhance the same menu twice.
                 if (menuRoot.data("menubarEnhanced") !== true) {
index 2dabb3c..5a2648b 100644 (file)
@@ -61,7 +61,7 @@ define(['jquery', 'core/ajax', 'core/str', 'tool_lp/competencypicker', 'core/tem
     /**
      * Set the parent competency in the competency form.
      *
-     * @param {Object} Data containing selected cmpetency.
+     * @param {Object} data Data containing selected competency.
      * @method setParent
      */
     ParentCompetencyForm.prototype.setParent = function(data) {
@@ -129,7 +129,7 @@ define(['jquery', 'core/ajax', 'core/str', 'tool_lp/competencypicker', 'core/tem
         /**
          * Main initialisation.
          * @param {String} buttonSelector The parent competency button selector.
-         * @param {String} inputHiddenSelector The hidden input field selector.
+         * @param {String} inputSelector The hidden input field selector.
          * @param {String} staticElementSelector The static element displaying the parent competency.
          * @param {Number} frameworkId The competency framework ID.
          * @param {Number} pageContextId The page context ID.
index 8d3e050..39efd98 100644 (file)
@@ -178,7 +178,7 @@ define(['jquery',
                 { key: 'deleteplan', component: 'tool_lp', param: plan.name },
                 { key: 'delete', component: 'moodle' },
                 { key: 'cancel', component: 'moodle' }
-            ]).done(function (strings) {
+            ]).done(function(strings) {
                 notification.confirm(
                     strings[0], // Confirm.
                     strings[1], // Delete plan X?
@@ -225,7 +225,7 @@ define(['jquery',
                 { key: 'reopenplanconfirm', component: 'tool_lp', param: plan.name },
                 { key: 'reopenplan', component: 'tool_lp' },
                 { key: 'cancel', component: 'moodle' }
-            ]).done(function (strings) {
+            ]).done(function(strings) {
                 notification.confirm(
                     strings[0], // Confirm.
                     strings[1], // Reopen plan X?
@@ -272,7 +272,7 @@ define(['jquery',
                 { key: 'completeplanconfirm', component: 'tool_lp', param: plan.name },
                 { key: 'completeplan', component: 'tool_lp' },
                 { key: 'cancel', component: 'moodle' }
-            ]).done(function (strings) {
+            ]).done(function(strings) {
                 notification.confirm(
                     strings[0], // Confirm.
                     strings[1], // Complete plan X?
@@ -318,7 +318,7 @@ define(['jquery',
                 { key: 'unlinkplantemplateconfirm', component: 'tool_lp', param: plan.name },
                 { key: 'unlinkplantemplate', component: 'tool_lp' },
                 { key: 'cancel', component: 'moodle' }
-            ]).done(function (strings) {
+            ]).done(function(strings) {
                 notification.confirm(
                     strings[0], // Confirm.
                     strings[1], // Unlink plan X?
@@ -507,7 +507,7 @@ define(['jquery',
                 courses: courses
             };
             templates.render('tool_lp/linked_courses_summary', context).done(function(html) {
-                str.get_string('linkedcourses', 'tool_lp').done(function (linkedcourses) {
+                str.get_string('linkedcourses', 'tool_lp').done(function(linkedcourses) {
                     new Dialogue(
                         linkedcourses, // Title.
                         html // The linked courses.
index 11c8568..9ba1fd4 100644 (file)
@@ -198,7 +198,6 @@ define(['jquery', 'core/notification', 'core/templates', 'core/ajax', 'tool_lp/d
      *
      * @name   scaleChangeHandler
      * @param  {Event} e
-     * @return {Void}
      * @function
      */
     ScaleConfig.prototype.scaleChangeHandler = function(e) {
index d27abd3..46011f8 100644 (file)
@@ -32,7 +32,7 @@ define(['jquery', 'core/ajax'], function($, ajax) {
          * @param {Number} scaleid The scale id
          * @return [] {Promise}
          */
-
+        // eslint-disable-next-line camelcase
         get_values: function(scaleid) {
 
             var deferred = $.Deferred();
@@ -40,7 +40,7 @@ define(['jquery', 'core/ajax'], function($, ajax) {
             if (typeof localCache[scaleid] === 'undefined') {
                 ajax.call([{
                     methodname: 'core_competency_get_scale_values',
-                    args: {scaleid : scaleid},
+                    args: {scaleid: scaleid},
                     done: function(scaleinfo) {
                         localCache[scaleid] = scaleinfo;
                         deferred.resolve(scaleinfo);
index 0966cc2..e0ceecb 100644 (file)
@@ -21,7 +21,7 @@
  * @copyright  2015 Damyon Wiese <damyon@moodle.com>
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-define(['jquery', 'core/templates', 'core/ajax', 'core/notification', 'core/str','tool_lp/actionselector'],
+define(['jquery', 'core/templates', 'core/ajax', 'core/notification', 'core/str', 'tool_lp/actionselector'],
        function($, templates, ajax, notification, str, Actionselector) {
     // Private variables and functions.
 
@@ -135,9 +135,9 @@ define(['jquery', 'core/templates', 'core/ajax', 'core/notification', 'core/str'
                         { key: 'unlinkplanstemplate', component: 'tool_lp' },
                         { key: 'confirm', component: 'moodle' },
                         { key: 'cancel', component: 'moodle' }
-                    ]).done(function (strings) {
-                        var actions = [{'text': strings[2], 'value' : 'delete'},
-                                       {'text': strings[3], 'value' : 'unlink'}];
+                    ]).done(function(strings) {
+                        var actions = [{'text': strings[2], 'value': 'delete'},
+                                       {'text': strings[3], 'value': 'unlink'}];
                         var actionselector = new Actionselector(
                                 strings[0], // Title.
                                 strings[1], // Message
@@ -152,14 +152,13 @@ define(['jquery', 'core/templates', 'core/ajax', 'core/notification', 'core/str'
                             doDelete();
                         });
                     }).fail(notification.exception);
-                }
-                else {
+                } else {
                     str.get_strings([
                         { key: 'confirm', component: 'moodle' },
                         { key: 'deletetemplate', component: 'tool_lp', param: template.shortname },
                         { key: 'delete', component: 'moodle' },
                         { key: 'cancel', component: 'moodle' }
-                    ]).done(function (strings) {
+                    ]).done(function(strings) {
                         notification.confirm(
                         strings[0], // Confirm.
                         strings[1], // Delete learning plan template X?
index f52934d..179dcf6 100644 (file)
@@ -186,12 +186,12 @@ define(['jquery', 'core/url', 'core/log'], function($, url, log) {
             var oneItem = null;
 
             while (lastIndex < currentIndex) {
-                oneItem  = $(this.visibleItems.get(lastIndex));
+                oneItem = $(this.visibleItems.get(lastIndex));
                 oneItem.attr('aria-selected', 'true');
                 lastIndex++;
             }
             while (lastIndex > currentIndex) {
-                oneItem  = $(this.visibleItems.get(lastIndex));
+                oneItem = $(this.visibleItems.get(lastIndex));
                 oneItem.attr('aria-selected', 'true');
                 lastIndex--;
             }
@@ -230,7 +230,8 @@ define(['jquery', 'core/url', 'core/log'], function($, url, log) {
      */
     Tree.prototype.toggleItem = function(item) {
         if (!this.multiSelect) {
-            return this.selectItem(item);
+            this.selectItem(item);
+            return;
         }
 
         var current = item.attr('aria-selected');
@@ -271,6 +272,7 @@ define(['jquery', 'core/url', 'core/log'], function($, url, log) {
      * @method handleKeyDown
      * @param {Object} item is the jquery id of the parent item of the group
      * @param {Event} e The event.
+     * @return {Boolean}
      */
     Tree.prototype.handleKeyDown = function(item, e) {
         var currentIndex = this.visibleItems.index(item);
@@ -419,6 +421,7 @@ define(['jquery', 'core/url', 'core/log'], function($, url, log) {
      * @method handleKeyPress
      * @param {Object} item is the jquery id of the parent item of the group
      * @param {Event} e The event.
+     * @return {Boolean}
      */
     Tree.prototype.handleKeyPress = function(item, e) {
         if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) {
@@ -454,7 +457,7 @@ define(['jquery', 'core/url', 'core/log'], function($, url, log) {
 
                 // Iterate through the menu items (starting from the current item and wrapping) until a match is found
                 // or the loop returns to the current menu item.
-                while (currentIndex != itemIndex)  {
+                while (currentIndex != itemIndex) {
 
                     var currentItem = this.visibleItems.eq(currentIndex);
                     var titleChr = currentItem.text().charAt(0);
@@ -468,7 +471,7 @@ define(['jquery', 'core/url', 'core/log'], function($, url, log) {
                         break;
                     }
 
-                    currentIndex = currentIndex+1;
+                    currentIndex = currentIndex + 1;
                     if (currentIndex == itemCount) {
                         // Reached the end of the list, start again at the beginning.
                         currentIndex = 0;
@@ -483,6 +486,7 @@ define(['jquery', 'core/url', 'core/log'], function($, url, log) {
             }
         }
 
+        // eslint-disable-next-line no-unreachable
         return true;
     };
 
@@ -507,6 +511,7 @@ define(['jquery', 'core/url', 'core/log'], function($, url, log) {
      * @method handleDblClick
      * @param {Object} item is the jquery id of the parent item of the group
      * @param {Event} e The event.
+     * @return {Boolean}
      */
     Tree.prototype.handleDblClick = function(item, e) {
 
@@ -531,6 +536,7 @@ define(['jquery', 'core/url', 'core/log'], function($, url, log) {
      * @method handleExpandCollapseClick
      * @param {Object} item is the jquery id of the parent item of the group
      * @param {Event} e The event.
+     * @return {Boolean}
      */
     Tree.prototype.handleExpandCollapseClick = function(item, e) {
 
@@ -547,6 +553,7 @@ define(['jquery', 'core/url', 'core/log'], function($, url, log) {
      * @method handleClick
      * @param {Object} item is the jquery id of the parent item of the group
      * @param {Event} e The event.
+     * @return {Boolean}
      */
     Tree.prototype.handleClick = function(item, e) {
 
@@ -568,6 +575,7 @@ define(['jquery', 'core/url', 'core/log'], function($, url, log) {
      * @method handleBlur
      * @param {Object} item item is the jquery id of the parent item of the group
      * @param {Event} e The event.
+     * @return {Boolean}
      */
     Tree.prototype.handleBlur = function() {
         return true;
@@ -579,6 +587,7 @@ define(['jquery', 'core/url', 'core/log'], function($, url, log) {
      * @method handleFocus
      * @param {Object} item item is the jquery id of the parent item of the group
      * @param {Event} e The event.
+     * @return {Boolean}
      */
     Tree.prototype.handleFocus = function(item) {
 
index bf61fe5..a560da7 100644 (file)
@@ -26,12 +26,12 @@ define(['jquery'], function($) {
     /**
      * UserCompetencyCourseNavigation
      *
-     * @param {String} The selector of the user element.
-     * @param {String} The selector of the competency element.
-     * @param {String} The base url for the page (no params).
-     * @param {Number} The course id
-     * @param {Number} The user id
-     * @param {Number} The competency id
+     * @param {String} userSelector The selector of the user element.
+     * @param {String} competencySelector The selector of the competency element.
+     * @param {String} baseUrl The base url for the page (no params).
+     * @param {Number} userId The user id
+     * @param {Number} competencyId The competency id
+     * @param {Number} courseId The course id
      */
     var UserCompetencyCourseNavigation = function(userSelector, competencySelector, baseUrl, userId, competencyId, courseId) {
         this._baseUrl = baseUrl;
index 2234414..334e8ff 100644 (file)
@@ -26,12 +26,12 @@ define(['jquery', 'core/notification', 'core/ajax', 'core/templates'], function(
     /**
      * Info
      *
-     * @param {JQuery} Selector to replace when the information needs updating.
-     * @param {Number} The id of the competency.
-     * @param {Number} The id of the user.
-     * @param {Number} The id of the plan.
-     * @param {Number} The id of the course.
-     * @param {Boolean} If we should display the user info.
+     * @param {JQuery} rootElement Selector to replace when the information needs updating.
+     * @param {Number} competencyId The id of the competency.
+     * @param {Number} userId The id of the user.
+     * @param {Number} planId The id of the plan.
+     * @param {Number} courseId The id of the course.
+     * @param {Boolean} displayuser If we should display the user info.
      */
     var Info = function(rootElement, competencyId, userId, planId, courseId, displayuser) {
         this._rootElement = rootElement;
index 87c977c..5001991 100644 (file)
@@ -27,9 +27,9 @@ define(['jquery', 'core/notification', 'core/str', 'core/ajax', 'core/templates'
     /**
      * UserCompetencyPopup
      *
-     * @param {String} The regionSelector
-     * @param {String} The userCompetencySelector
-     * @param {Number} The plan ID
+     * @param {String} regionSelector The regionSelector
+     * @param {String} userCompetencySelector The userCompetencySelector
+     * @param {Number} planId The plan ID
      */
     var UserCompetencyPopup = function(regionSelector, userCompetencySelector, planId) {
         this._regionSelector = regionSelector;
@@ -53,14 +53,14 @@ define(['jquery', 'core/notification', 'core/str', 'core/ajax', 'core/templates'
         var planId = this._planId;
 
         var requests = ajax.call([{
-            methodname : 'tool_lp_data_for_user_competency_summary_in_plan',
+            methodname: 'tool_lp_data_for_user_competency_summary_in_plan',
             args: { competencyid: competencyId, planid: planId },
             done: this._contextLoaded.bind(this),
             fail: notification.exception
         }]);
 
         // Log the user competency viewed in plan event.
-        requests[0].then(function (result) {
+        requests[0].then(function(result) {
             var eventMethodName = 'core_competency_user_competency_viewed_in_plan';
             // Trigger core_competency_user_competency_plan_viewed event instead if plan is already completed.
             if (result.plan.iscompleted) {
@@ -98,7 +98,7 @@ define(['jquery', 'core/notification', 'core/str', 'core/ajax', 'core/templates'
         var planId = this._planId;
 
         ajax.call([{
-            methodname : 'tool_lp_data_for_plan_page',
+            methodname: 'tool_lp_data_for_plan_page',
             args: { planid: planId},
             done: this._pageContextLoaded.bind(this),
             fail: notification.exception
index 6c4376a..f61c0f9 100644 (file)
@@ -47,7 +47,6 @@ define(['jquery',
      * Cancel a review request and refresh the view.
      *
      * @param  {Object} data The user competency data.
-     * @return {Void}
      * @method _cancelReviewRequest
      */
     UserCompetencyWorkflow.prototype._cancelReviewRequest = function(data) {
@@ -71,7 +70,6 @@ define(['jquery',
      * Cancel a review request an refresh the view.
      *
      * @param  {Object} data The user competency data.
-     * @return {Void}
      * @method cancelReviewRequest
      */
     UserCompetencyWorkflow.prototype.cancelReviewRequest = function(data) {
@@ -82,7 +80,6 @@ define(['jquery',
      * Cancel a review request handler.
      *
      * @param  {Event} e The event.
-     * @return {Void}
      * @method _cancelReviewRequestHandler
      */
     UserCompetencyWorkflow.prototype._cancelReviewRequestHandler = function(e) {
@@ -95,7 +92,6 @@ define(['jquery',
      * Request a review and refresh the view.
      *
      * @param  {Object} data The user competency data.
-     * @return {Void}
      * @method _requestReview
      */
     UserCompetencyWorkflow.prototype._requestReview = function(data) {
@@ -119,7 +115,6 @@ define(['jquery',
      * Request a review.
      *
      * @param  {Object} data The user competency data.
-     * @return {Void}
      * @method requestReview
      */
     UserCompetencyWorkflow.prototype.requestReview = function(data) {
@@ -130,7 +125,6 @@ define(['jquery',
      * Request a review handler.
      *
      * @param  {Event} e The event.
-     * @return {Void}
      * @method _requestReviewHandler
      */
     UserCompetencyWorkflow.prototype._requestReviewHandler = function(e) {
@@ -143,7 +137,6 @@ define(['jquery',
      * Start a review and refresh the view.
      *
      * @param  {Object} data The user competency data.
-     * @return {Void}
      * @method _startReview
      */
     UserCompetencyWorkflow.prototype._startReview = function(data) {
@@ -167,7 +160,6 @@ define(['jquery',
      * Start a review.
      *
      * @param  {Object} data The user competency data.
-     * @return {Void}
      * @method startReview
      */
     UserCompetencyWorkflow.prototype.startReview = function(data) {
@@ -178,7 +170,6 @@ define(['jquery',
      * Start a review handler.
      *
      * @param  {Event} e The event.
-     * @return {Void}
      * @method _startReviewHandler
      */
     UserCompetencyWorkflow.prototype._startReviewHandler = function(e) {
@@ -191,7 +182,6 @@ define(['jquery',
      * Stop a review and refresh the view.
      *
      * @param  {Object} data The user competency data.
-     * @return {Void}
      * @method _stopReview
      */
     UserCompetencyWorkflow.prototype._stopReview = function(data) {
@@ -215,7 +205,6 @@ define(['jquery',
      * Stop a review.
      *
      * @param  {Object} data The user competency data.
-     * @return {Void}
      * @method stopReview
      */
     UserCompetencyWorkflow.prototype.stopReview = function(data) {
@@ -226,7 +215,6 @@ define(['jquery',
      * Stop a review handler.
      *
      * @param  {Event} e The event.
-     * @return {Void}
      * @method _stopReviewHandler
      */
     UserCompetencyWorkflow.prototype._stopReviewHandler = function(e) {
index 8c9e47e..f1f5cd3 100644 (file)
@@ -165,7 +165,7 @@ define(['jquery',
                 { key: 'deleteuserevidence', component: 'tool_lp', param: evidence.name },
                 { key: 'delete', component: 'moodle' },
                 { key: 'cancel', component: 'moodle' }
-            ]).done(function (strings) {
+            ]).done(function(strings) {
                 notification.confirm(
                     strings[0], // Confirm.
                     strings[1], // Delete evidence X?
@@ -320,7 +320,7 @@ define(['jquery',
                 { key: 'sendallcompetenciestoreview', component: 'tool_lp', param: evidence.name },
                 { key: 'confirm', component: 'moodle' },
                 { key: 'cancel', component: 'moodle' }
-            ]).done(function (strings) {
+            ]).done(function(strings) {
                 notification.confirm(
                     strings[0], // Confirm.
                     strings[1], // Send all competencies in review for X?
index dfd24fd..e776a1a 100644 (file)
@@ -40,14 +40,18 @@ class course_module_summary_exporter extends \core_competency\external\exporter
     }
 
     protected function get_other_values(renderer_base $output) {
-        $context = $this->related['cm']->context;
+        $cm = $this->related['cm'];
+        $context = $cm->context;
 
-        return array(
-            'id' => $this->related['cm']->id,
-            'name' => external_format_string($this->related['cm']->name, $context->id),
-            'url' => $this->related['cm']->url->out(),
-            'iconurl' => $this->related['cm']->get_icon_url()->out()
+        $values = array(
+            'id' => $cm->id,
+            'name' => external_format_string($cm->name, $context->id),
+            'iconurl' => $cm->get_icon_url()->out()
         );
+        if ($cm->url) {
+            $values['url'] = $cm->url->out();
+        }
+        return $values;
     }
 
 
@@ -60,7 +64,8 @@ class course_module_summary_exporter extends \core_competency\external\exporter
                 'type' => PARAM_TEXT
             ),
             'url' => array(
-                'type' => PARAM_URL
+                'type' => PARAM_URL,
+                'optional' => true,
             ),
             'iconurl' => array(
                 'type' => PARAM_URL
index 4beab69..e700a17 100644 (file)
@@ -40,11 +40,11 @@ $url = new moodle_url('/admin/tool/lp/coursecompetencies.php', $urlparams);
 list($title, $subtitle) = \tool_lp\page_helper::setup_for_course($url, $course);
 
 $output = $PAGE->get_renderer('tool_lp');
+$page = new \tool_lp\output\course_competencies_page($course->id);
+
 echo $output->header();
 echo $output->heading($title);
 
-
-$page = new \tool_lp\output\course_competencies_page($course->id);
 echo $output->render($page);
 
 echo $output->footer();
index 870104e..62b6892 100644 (file)
@@ -36,6 +36,13 @@ function tool_lp_extend_navigation_course($navigation, $course, $coursecontext)
         return;
     }
 
+    // Check access to the course and competencies page.
+    $capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage');
+    $context = context_course::instance($course->id);
+    if (!has_any_capability($capabilities, $context) || !can_access_course($course)) {
+        return;
+    }
+
     // Just a link to course competency.
     $title = get_string('competencies', 'core_competency');
     $path = new moodle_url("/admin/tool/lp/coursecompetencies.php", array('courseid' => $course->id));
index 39d4e6e..9f13f6a 100644 (file)
 
 .tool-lp-menu li {
     float: left;
-    display: inline;
     position: relative;
     list-style-type: none;
     white-space: nowrap;
index 166fe5d..5a17c57 100644 (file)
@@ -50,7 +50,8 @@ class tool_mobile_external_testcase extends externallib_advanced_testcase {
         $result = external::get_plugins_supporting_mobile();
         $result = external_api::clean_returnvalue(external::get_plugins_supporting_mobile_returns(), $result);
         $this->assertCount(0, $result['warnings']);
-        $this->assertCount(0, $result['plugins']);
+        $this->assertArrayHasKey('plugins', $result);
+        $this->assertTrue(is_array($result['plugins']));
     }
 
 }
index c11665e..793b11f 100644 (file)
@@ -140,6 +140,10 @@ class eventobservers {
             $subscriptions = subscription_manager::get_subscriptions_by_event($eventobj);
             $idstosend = array();
             foreach ($subscriptions as $subscription) {
+                // Only proceed to fire events and notifications if the subscription is active.
+                if (!subscription_manager::subscription_is_active($subscription)) {
+                    continue;
+                }
                 $starttime = $now - $subscription->timewindow;
                 $starttime = ($starttime > $subscription->lastnotificationsent) ? $starttime : $subscription->lastnotificationsent;
                 if ($subscription->courseid == 0) {
index 6e25f6e..98518e9 100644 (file)
@@ -55,17 +55,26 @@ class subscription {
      * Magic get method.
      *
      * @param string $prop property to get.
-     *
      * @return mixed
      * @throws \coding_exception
      */
     public function __get($prop) {
-        if (property_exists($this->subscription, $prop)) {
+        if (isset($this->subscription->$prop)) {
             return $this->subscription->$prop;
         }
         throw new \coding_exception('Property "' . $prop . '" doesn\'t exist');
     }
 
+    /**
+     * Magic isset method.
+     *
+     * @param string $prop the property to get.
+     * @return bool true if the property is set, false otherwise.
+     */
+    public function __isset($prop) {
+        return property_exists($this->subscription, $prop);
+    }
+
     /**
      * Get a human readable name for instances associated with this subscription.
      *
index 421984c..c4382e7 100644 (file)
@@ -35,6 +35,10 @@ defined('MOODLE_INTERNAL') || die();
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 class subscription_manager {
+
+    /** @const Period of time, in days, after which an inactive subscription will be removed completely.*/
+    const INACTIVE_SUBSCRIPTION_LIFESPAN_IN_DAYS = 30;
+
     /**
      * Subscribe a user to a given rule.
      *
@@ -456,4 +460,78 @@ class subscription_manager {
 
         return false;
     }
+
+    /**
+     * Activates a group of subscriptions based on an input array of ids.
+     *
+     * @since 3.2.0
+     * @param array $ids of subscription ids.
+     * @return bool true if the operation was successful, false otherwise.
+     */
+    public static function activate_subscriptions(array $ids) {
+        global $DB;
+        if (!empty($ids)) {
+            list($sql, $params) = $DB->get_in_or_equal($ids);
+            $success = $DB->set_field_select('tool_monitor_subscriptions', 'inactivedate', '0', 'id ' . $sql, $params);
+            return $success;
+        }
+        return false;
+    }
+
+    /**
+     * Deactivates a group of subscriptions based on an input array of ids.
+     *
+     * @since 3.2.0
+     * @param array $ids of subscription ids.
+     * @return bool true if the operation was successful, false otherwise.
+     */
+    public static function deactivate_subscriptions(array $ids) {
+        global $DB;
+        if (!empty($ids)) {
+            $inactivedate = time();
+            list($sql, $params) = $DB->get_in_or_equal($ids);
+            $success = $DB->set_field_select('tool_monitor_subscriptions', 'inactivedate', $inactivedate, 'id ' . $sql,
+                                             $params);
+            return $success;
+        }
+        return false;
+    }
+
+    /**
+     * Deletes subscriptions which have been inactive for a period of time.
+     *
+     * @since 3.2.0
+     * @param int $userid if provided, only this user's stale subscriptions will be deleted.
+     * @return bool true if the operation was successful, false otherwise.
+     */
+    public static function delete_stale_subscriptions($userid = 0) {
+        global $DB;
+        // Get the expiry duration, in days.
+        $cutofftime = strtotime("-" . self::INACTIVE_SUBSCRIPTION_LIFESPAN_IN_DAYS . " days", time());
+
+        if (!empty($userid)) {
+            // Remove any stale subscriptions for the desired user only.
+            $success = $DB->delete_records_select('tool_monitor_subscriptions',
+                                                  'userid = ? AND inactivedate < ? AND inactivedate <> 0',
+                                                  array($userid, $cutofftime));
+
+        } else {
+            // Remove all stale subscriptions.
+            $success = $DB->delete_records_select('tool_monitor_subscriptions',
+                                                  'inactivedate < ? AND inactivedate <> 0',
+                                                  array($cutofftime));
+        }
+        return $success;
+    }
+
+    /**
+     * Check whether a subscription is active.
+     *
+     * @since 3.2.0
+     * @param \tool_monitor\subscription $subscription instance.
+     * @return bool true if the subscription is active, false otherwise.
+     */
+    public static function subscription_is_active(subscription $subscription) {
+        return empty($subscription->inactivedate);
+    }
 }
diff --git a/admin/tool/monitor/classes/task/check_subscriptions.php b/admin/tool/monitor/classes/task/check_subscriptions.php
new file mode 100644 (file)
index 0000000..8262f10
--- /dev/null
@@ -0,0 +1,274 @@
+<?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/>.
+namespace tool_monitor\task;
+use tool_monitor\subscription;
+use tool_monitor\subscription_manager;
+
+/**
+ * Simple task class responsible for activating, deactivating and removing subscriptions.
+ *
+ * Activation/deactivation is managed by looking at the same access rules used to determine whether a user can
+ * subscribe to the rule in the first place.
+ *
+ * Removal occurs when a subscription has been inactive for a period of time exceeding the lifespan, as set by
+ * subscription_manager::get_inactive_subscription_lifespan().
+ *
+ * I.e.
+ *  - Activation:   If a user can subscribe currently, then an existing subscription should be made active.
+ *  - Deactivation: If a user cannot subscribe currently, then an existing subscription should be made inactive.
+ *  - Removal:      If a user has a subscription that has been inactive for longer than the prescribed period, then
+ *                  delete the subscription entirely.
+ *
+ * @since      3.2.0
+ * @package    tool_monitor
+ * @copyright  2016 Jake Dallimore <jrhdallimore@gmail.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class check_subscriptions extends \core\task\scheduled_task {
+
+    /** @var array 1d static cache, indexed by userid, storing whether or not the user has been fully set up.*/
+    protected $userssetupcache = array();
+
+    /** @var array 2d static cache, indexed by courseid and userid, storing whether a user can access the course with
+     *  the 'tool/monitor:subscribe' capability.
+     */
+    protected $courseaccesscache = array();
+
+    /**
+     * Get a descriptive name for this task.
+     *
+     * @since 3.2.0
+     * @return string name of the task.
+     */
+    public function get_name() {
+        return get_string('taskchecksubscriptions', 'tool_monitor');
+    }
+
+    /**
+     * Checks all course-level rule subscriptions and activates/deactivates based on current course access.
+     *
+     * The ordering of checks within the task is important for optimisation purposes. The aim is to be able to make a decision
+     * about whether to activate/deactivate each subscription without making unnecessary checks. The ordering roughly follows the
+     * context model, starting with system and user checks and moving down to course and course-module only when necessary.
+     *
+     * For example, if the user is suspended, then any active subscription is made inactive right away. I.e. there is no need to
+     * check site-level, course-level or course-module-level permissions. Likewise, if a subscriptions is site-level, there is no
+     * need to check course-level and course-module-level permissions.
+     *
+     * The task performs the following checks, in this order:
+     * 1. Check for a suspended user, breaking if suspended.
+     * 2. Check for an incomplete (not set up) user, breaking if not fully set up.
+     * 3. Check for the required capability in the relevant context, breaking if the capability is not found.
+     * 4. Check whether the subscription is site-context, breaking if true.
+     * 5. Check whether the user has course access, breaking only if the subscription is not also course-module-level.
+     * 6. Check whether the user has course-module access.
+     *
+     * @since 3.2.0
+     */
+    public function execute() {
+        global $DB;
+
+        if (!get_config('tool_monitor', 'enablemonitor')) {
+            return; // The tool is disabled. Nothing to do.
+        }
+
+        $toactivate   = array(); // Store the ids of subscriptions to be activated upon completion.
+        $todeactivate = array(); // Store the ids of subscriptions to be deactivated upon completion.
+
+        // Resultset rows are ordered by userid and courseid to work nicely with get_fast_modinfo() caching.
+        $sql = "SELECT u.id AS userid, u.firstname AS userfirstname, u.lastname AS userlastname, u.suspended AS usersuspended,
+                       u.email AS useremail, c.visible as coursevisible, c.cacherev as coursecacherev, s.courseid AS subcourseid,
+                       s.userid AS subuserid, s.cmid AS subcmid, s.inactivedate AS subinactivedate, s.id AS subid
+                  FROM {user} u
+                  JOIN {tool_monitor_subscriptions} s ON (s.userid = u.id)
+             LEFT JOIN {course} c ON (c.id = s.courseid)
+                 WHERE u.id = s.userid
+              ORDER BY s.userid, s.courseid";
+        $rs = $DB->get_recordset_sql($sql);
+
+        foreach ($rs as $row) {
+            // Create skeleton records from the result. This should be enough to use in subsequent access calls and avoids DB hits.
+            $sub = $this->get_subscription_from_rowdata($row);
+            $sub = new subscription($sub);
+            if (!isset($user) || $user->id != $sub->userid) {
+                $user= $this->get_user_from_rowdata($row);
+            }
+            if ((!isset($course) || $course->id != $sub->courseid) && !empty($sub->courseid)) {
+                $course = $this->get_course_from_rowdata($row);
+            }
+
+            // The user is suspended at site level, so deactivate any active subscriptions.
+            if ($user->suspended) {
+                if (subscription_manager::subscription_is_active($sub)) {
+                    $todeactivate[] = $sub->id;
+                }
+                continue;
+            }
+
+            // Is the user fully set up? As per require_login on the subscriptions page.
+            if (!$this->is_user_setup($user)) {
+                if (subscription_manager::subscription_is_active($sub)) {
+                    $todeactivate[] = $sub->id;
+                }
+                continue;
+            }
+
+            // Determine the context, based on the subscription course id.
+            $sitelevelsubscription = false;
+            if (empty($sub->courseid)) {
+                $context = \context_system::instance();
+                $sitelevelsubscription = true;
+            } else {
+                $context = \context_course::instance($sub->courseid);
+            }
+
+            // Check capability in the context.
+            if (!has_capability('tool/monitor:subscribe', $context, $user)) {
+                if (subscription_manager::subscription_is_active($sub)) {
+                    $todeactivate[] = $sub->id;
+                }
+                continue;
+            }
+
+            // If the subscription is site-level, then we've run all the checks required to make an access decision.
+            if ($sitelevelsubscription) {
+                if (!subscription_manager::subscription_is_active($sub)) {
+                    $toactivate[] = $sub->id;
+                }
+                continue;
+            }
+
+            // Check course access.
+            if (!$this->user_can_access_course($user, $course, 'tool/monitor:subscribe')) {
+                if (subscription_manager::subscription_is_active($sub)) {
+                    $todeactivate[] = $sub->id;
+                }
+                continue;
+            }
+
+            // If the subscription has no course module relationship.
+            if (empty($sub->cmid)) {
+                if (!subscription_manager::subscription_is_active($sub)) {
+                    $toactivate[] = $sub->id;
+                }
+                continue;
+            }
+
+            // Otherwise, check the course module info. We use the same checks as on the subscription page.
+            $modinfo = get_fast_modinfo($course, $sub->userid);
+            $cm = $modinfo->get_cm($sub->cmid);
+            if (!$cm || !$cm->uservisible || !$cm->available) {
+                if (subscription_manager::subscription_is_active($sub)) {
+                    $todeactivate[] = $sub->id;
+                }
+                continue;
+            }
+
+            // The course module is available and visible, so make a decision.
+            if (!subscription_manager::subscription_is_active($sub)) {
+                $toactivate[] = $sub->id;
+            }
+        }
+        $rs->close();
+
+        // Activate/deactivate/delete relevant subscriptions.
+        subscription_manager::activate_subscriptions($toactivate);
+        subscription_manager::deactivate_subscriptions($todeactivate);
+        subscription_manager::delete_stale_subscriptions();
+    }
+
+    /**
+     * Determines whether a user is fully set up, using cached results where possible.
+     *
+     * @since 3.2.0
+     * @param \stdClass $user the user record.
+     * @return bool true if the user is fully set up, false otherwise.
+     */
+    protected function is_user_setup($user) {
+        if (!isset($this->userssetupcache[$user->id])) {
+            $this->userssetupcache[$user->id] = !user_not_fully_set_up($user);
+        }
+        return $this->userssetupcache[$user->id];
+    }
+
+    /**
+     * Determines a user's access to a course with a given capability, using cached results where possible.
+     *
+     * @since 3.2.0
+     * @param \stdClass $user the user record.
+     * @param \stdClass $course the course record.
+     * @param string $capability the capability to check.
+     * @return bool true if the user can access the course with the specified capability, false otherwise.
+     */
+    protected function user_can_access_course($user, $course, $capability) {
+        if (!isset($this->courseaccesscache[$course->id][$user->id][$capability])) {
+            $this->courseaccesscache[$course->id][$user->id][$capability] = can_access_course($course, $user, $capability, true);
+        }
+        return $this->courseaccesscache[$course->id][$user->id][$capability];
+    }
+
+    /**
+     * Returns a partial subscription record, created from properties of the supplied recordset row object.
+     * Intended to return a minimal record for specific use within this class and in subsequent access control calls only.
+     *
+     * @since 3.2.0
+     * @param \stdClass $rowdata the row object.
+     * @return \stdClass a partial subscription record.
+     */
+    protected function get_subscription_from_rowdata($rowdata) {
+        $sub = new \stdClass();
+        $sub->id = $rowdata->subid;
+        $sub->userid = $rowdata->subuserid;
+        $sub->courseid = $rowdata->subcourseid;
+        $sub->cmid = $rowdata->subcmid;
+        $sub->inactivedate = $rowdata->subinactivedate;
+        return $sub;
+    }
+
+    /**
+     * Returns a partial course record, created from properties of the supplied recordset row object.
+     * Intended to return a minimal record for specific use within this class and in subsequent access control calls only.
+     *
+     * @since 3.2.0
+     * @param \stdClass $rowdata the row object.
+     * @return \stdClass a partial course record.
+     */
+    protected function get_course_from_rowdata($rowdata) {
+        $course = new \stdClass();
+        $course->id = $rowdata->subcourseid;
+        $course->visible = $rowdata->coursevisible;
+        $course->cacherev = $rowdata->coursecacherev;
+        return $course;
+    }
+
+    /**
+     * Returns a partial user record, created from properties of the supplied recordset row object.
+     * Intended to return a minimal record for specific use within this class and in subsequent access control calls only.
+     *
+     * @since 3.2.0
+     * @param \stdClass $rowdata the row object.
+     * @return \stdClass a partial user record.
+     */
+    protected function get_user_from_rowdata($rowdata) {
+        $user = new \stdClass();
+        $user->id = $rowdata->userid;
+        $user->firstname = $rowdata->userfirstname;
+        $user->lastname = $rowdata->userlastname;
+        $user->email = $rowdata->useremail;
+        $user->suspended = $rowdata->usersuspended;
+        return $user;
+    }
+}
index 7729199..9a3f1a1 100644 (file)
@@ -38,6 +38,7 @@
         <FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="User id of the subscriber"/>
         <FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="Timestamp of when this subscription was created"/>
         <FIELD NAME="lastnotificationsent" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Timestamp of the time when a notification was last sent for this subscription."/>
+        <FIELD NAME="inactivedate" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
       </FIELDS>
       <KEYS>
         <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
index e70c344..20f324d 100644 (file)
@@ -32,5 +32,14 @@ $tasks = array(
         'day' => '*',
         'dayofweek' => '*',
         'month' => '*'
+    ),
+    array(
+        'classname' => 'tool_monitor\task\check_subscriptions',
+        'blocking' => 0,
+        'minute' => 'R',
+        'hour' => 'R',
+        'day' => '*',
+        'dayofweek' => '*',
+        'month' => '*'
     )
 );
index 9f8e53b..aaea29a 100644 (file)
@@ -62,5 +62,20 @@ function xmldb_tool_monitor_upgrade($oldversion) {
     // Moodle v3.1.0 release upgrade line.
     // Put any upgrade step following this.
 
+    if ($oldversion < 2016052305) {
+
+        // Define field inactivedate to be added to tool_monitor_subscriptions.
+        $table = new xmldb_table('tool_monitor_subscriptions');
+        $field = new xmldb_field('inactivedate', XMLDB_TYPE_INTEGER, '10', null, true, null, 0, 'lastnotificationsent');
+
+        // Conditionally launch add field inactivedate.
+        if (!$dbman->field_exists($table, $field)) {
+            $dbman->add_field($table, $field);
+        }
+
+        // Monitor savepoint reached.
+        upgrade_plugin_savepoint(true, 2016052305, 'tool', 'monitor');
+    }
+
     return true;
 }
index c0fbf7f..fb47717 100644 (file)
@@ -99,4 +99,5 @@ $string['subhelp'] = 'Subscription details';
 $string['subhelp_help'] = 'This subscription listens for when the event \'{$a->eventname}\' has been triggered in \'{$a->moduleinstance}\' {$a->frequency} time(s) in {$a->minutes} minute(s).';
 $string['subscribeto'] = 'Subscribe to rule "{$a}"';
 $string['taskcleanevents'] = 'Removes any unnecessary event monitor events';
+$string['taskchecksubscriptions'] = 'Activate/deactivate invalid rule subscriptions';
 $string['unsubscribe'] = 'Unsubscribe';
diff --git a/admin/tool/monitor/tests/subscription_test.php b/admin/tool/monitor/tests/subscription_test.php
new file mode 100644 (file)
index 0000000..3548c7f
--- /dev/null
@@ -0,0 +1,65 @@
+<?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/>.
+defined('MOODLE_INTERNAL') || exit();
+
+/**
+ * Unit tests for the subscription class.
+ * @since 3.2.0
+ *
+ * @package    tool_monitor
+ * @category   test
+ * @copyright  2016 Jake Dallimore <jrhdallimore@gmail.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class tool_monitor_subscription_testcase extends advanced_testcase {
+
+    /**
+     * @var \tool_monitor\subscription $subscription object.
+     */
+    private $subscription;
+
+    /**
+     * Test set up.
+     */
+    public function setUp() {
+        $this->resetAfterTest(true);
+
+        // Create the mock subscription.
+        $sub = new stdClass();
+        $sub->id = 100;
+        $sub->name = 'My test rule';
+        $sub->courseid = 20;
+        $this->subscription = $this->getMock('\tool_monitor\subscription',null, array($sub));
+    }
+
+    /**
+     * Test for the magic __isset method.
+     */
+    public function test_magic_isset() {
+        $this->assertEquals(true, isset($this->subscription->name));
+        $this->assertEquals(true, isset($this->subscription->courseid));
+        $this->assertEquals(false, isset($this->subscription->ruleid));
+    }
+
+    /**
+     * Test for the magic __get method.
+     */
+    public function test_magic_get() {
+        $this->assertEquals(20, $this->subscription->courseid);
+        $this->setExpectedException('coding_exception');
+        $this->subscription->ruleid;
+    }
+}
diff --git a/admin/tool/monitor/tests/task_check_subscriptions_test.php b/admin/tool/monitor/tests/task_check_subscriptions_test.php
new file mode 100644 (file)
index 0000000..b526784
--- /dev/null
@@ -0,0 +1,364 @@
+<?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/>.
+defined('MOODLE_INTERNAL') || exit();
+
+/**
+ * Unit tests for the tool_monitor clean events task.
+ * @since 3.2.0
+ *
+ * @package    tool_monitor
+ * @category   test
+ * @copyright  2016 Jake Dallimore <jrhdallimore@gmail.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class tool_monitor_task_check_subscriptions_testcase extends advanced_testcase {
+
+    private $course;
+    private $user;
+    private $rule;
+    private $subscription;
+    private $teacherrole;
+    private $studentrole;
+
+    /**
+     * Test set up.
+     */
+    public function setUp() {
+        global $DB;
+        set_config('enablemonitor', 1, 'tool_monitor');
+        $this->resetAfterTest(true);
+
+        // All tests defined herein need a user, course, rule and subscription, so set these up.
+        $this->user = $this->getDataGenerator()->create_user();
+        $this->course = $this->getDataGenerator()->create_course();
+
+        $rule = new stdClass();
+        $rule->userid = 2; // Rule created by admin.
+        $rule->courseid = $this->course->id;
+        $rule->plugin = 'mod_book';
+        $rule->eventname = '\mod_book\event\course_module_viewed';
+        $rule->timewindow = 500;
+        $monitorgenerator = $this->getDataGenerator()->get_plugin_generator('tool_monitor');
+        $this->rule = $monitorgenerator->create_rule($rule);
+
+        $sub = new stdClass();
+        $sub->courseid = $this->course->id;
+        $sub->userid = $this->user->id;
+        $sub->ruleid = $this->rule->id;
+        $this->subscription = $monitorgenerator->create_subscription($sub);
+
+        // Also set up a student and a teacher role for use in some tests.
+        $this->teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
+        $this->studentrole = $DB->get_record('role', array('shortname' => 'student'));
+    }
+
+    /**
+     * Reloads the subscription object from the DB.
+     *
+     * @return void.
+     */
+    private function reload_subscription() {
+        global $DB;
+        $sub = $DB->get_record('tool_monitor_subscriptions', array('id' => $this->subscription->id));
+        $this->subscription = new \tool_monitor\subscription($sub);
+    }
+
+    /**
+     * Test to confirm the task is named correctly.
+     */
+    public function test_task_name() {
+        $task = new \tool_monitor\task\check_subscriptions();
+        $this->assertEquals(get_string('taskchecksubscriptions', 'tool_monitor'), $task->get_name());
+    }
+
+    /**
+     * Test to confirm that site level subscriptions are activated and deactivated according to system capabilities.
+     */
+    public function test_site_level_subscription() {
+        // Create a site level subscription.
+        $monitorgenerator = $this->getDataGenerator()->get_plugin_generator('tool_monitor');
+        $sub = new stdClass();
+        $sub->userid = $this->user->id;
+        $sub->ruleid = $this->rule->id;
+        $this->subscription = $monitorgenerator->create_subscription($sub);
+
+        // Run the task.
+        $task = new \tool_monitor\task\check_subscriptions();
+        $task->execute();
+
+        // The subscription should be inactive as the user doesn't have the capability. Pass in the id only to refetch the data.
+        $this->reload_subscription();
+        $this->assertEquals(false, \tool_monitor\subscription_manager::subscription_is_active($this->subscription));
+
+        // Now, assign the user as a teacher role at system context.
+        $this->getDataGenerator()->role_assign($this->teacherrole->id, $this->user->id, context_system::instance());
+
+        // Run the task.
+        $task = new \tool_monitor\task\check_subscriptions();
+        $task->execute();
+
+        // The subscription should be active now. Pass in the id only to refetch the data.
+        $this->reload_subscription();
+        $this->assertEquals(true, \tool_monitor\subscription_manager::subscription_is_active($this->subscription));
+    }
+
+    /**
+     * Test to confirm that if the module is disabled, no changes are made to active subscriptions.
+     */
+    public function test_module_disabled() {
+        set_config('enablemonitor', 0, 'tool_monitor');
+
+        // Subscription should be active to start with.
+        $this->assertEquals(true, \tool_monitor\subscription_manager::subscription_is_active($this->subscription));
+
+        // Run the task. Note, we never enrolled the user.
+        $task = new \tool_monitor\task\check_subscriptions();
+        $task->execute();
+
+        // The subscription should still be active. Pass in the id only to refetch the data.
+        $this->reload_subscription();
+        $this->assertEquals(true, \tool_monitor\subscription_manager::subscription_is_active($this->subscription));
+    }
+
+    /**
+     * Test to confirm an active, valid subscription stays active once the scheduled task is run.
+     */
+    public function test_active_unaffected() {
+        // Enrol the user as a teacher. This role should have the required capability.
+        $this->getDataGenerator()->enrol_user($this->user->id, $this->course->id, $this->teacherrole->id);
+
+        // Subscription should be active to start with.
+        $this->assertEquals(true, \tool_monitor\subscription_manager::subscription_is_active($this->subscription));
+
+        // Run the task.
+        $task = new \tool_monitor\task\check_subscriptions();
+        $task->execute();
+
+        // The subscription should still be active. Pass in the id only to refetch the data.
+        $this->reload_subscription();
+        $this->assertEquals(true, \tool_monitor\subscription_manager::subscription_is_active($this->subscription));
+    }
+
+    /**
+     * Test to confirm that a subscription for a user without an enrolment to the course is made inactive.
+     */
+    public function test_course_enrolment() {
+        // Subscription should be active until deactivated by the scheduled task. Remember, by default the test setup
+        // doesn't enrol the user, so the first run of the task should deactivate it.
+        $this->assertEquals(true, \tool_monitor\subscription_manager::subscription_is_active($this->subscription));
+
+        // Run the task.
+        $task = new \tool_monitor\task\check_subscriptions();
+        $task->execute();
+
+        // The subscription should NOT be active. Pass in the id only to refetch the data.
+        $this->reload_subscription();
+        $this->assertEquals(false, \tool_monitor\subscription_manager::subscription_is_active($this->subscription));
+
+        // Enrol the user.
+        $this->getDataGenerator()->enrol_user($this->user->id, $this->course->id, $this->teacherrole->id);
+
+        // Run the task.
+        $task = new \tool_monitor\task\check_subscriptions();
+        $task->execute();
+
+        // Subscription should now be active again.
+        $this->reload_subscription();
+        $this->assertEquals(true, \tool_monitor\subscription_manager::subscription_is_active($this->subscription));
+    }
+
+    /**
+     * Test to confirm that subscriptions for enrolled users without the required capability are made inactive.
+     */
+    public function test_enrolled_user_with_no_capability() {
+        // Enrol the user. By default, students won't have the required capability.
+        $this->getDataGenerator()->enrol_user($this->user->id, $this->course->id, $this->studentrole->id);
+
+        // The subscription should be active to start with. Pass in the id only to refetch the data.
+        $this->assertEquals(true, \tool_monitor\subscription_manager::subscription_is_active($this->subscription));
+
+        // Run the task.
+        $task = new \tool_monitor\task\check_subscriptions();
+        $task->execute();
+
+        // The subscription should NOT be active. Pass in the id only to refetch the data.
+        $this->reload_subscription();
+        $this->assertEquals(false, \tool_monitor\subscription_manager::subscription_is_active($this->subscription));
+    }
+
+    /**
+     * Test to confirm that subscriptions for users who fail can_access_course(), are deactivated.
+     */
+    public function test_can_access_course() {
+        // Enrol the user as a teacher. This role should have the required capability.
+        $this->getDataGenerator()->enrol_user($this->user->id, $this->course->id, $this->teacherrole->id);
+
+        // Strip the ability to see hidden courses, so we'll fail the check_subscriptions->user_can_access_course call.
+        $context = \context_course::instance($this->course->id);
+        assign_capability('moodle/course:viewhiddencourses', CAP_PROHIBIT, $this->teacherrole->id, $context);
+
+        // Subscription should be active to start with.
+        $this->assertEquals(true, \tool_monitor\subscription_manager::subscription_is_active($this->subscription));
+
+        // Hide the course.
+        course_change_visibility($this->course->id, false);
+
+        // Run the task.
+        $task = new \tool_monitor\task\check_subscriptions();
+        $task->execute();
+
+        // The subscription should be inactive. Pass in the id only to refetch the data.
+        $this->reload_subscription();
+        $this->assertEquals(false, \tool_monitor\subscription_manager::subscription_is_active($this->subscription));
+    }
+
+    /**
+     * Test to confirm that subscriptions for enrolled users who don't have CM access, are deactivated.
+     */
+    public function test_cm_access() {
+        // Enrol the user as a student but grant to ability to subscribe. Students cannot view hidden activities.
+        $context = \context_course::instance($this->course->id);
+        assign_capability('tool/monitor:subscribe', CAP_ALLOW, $this->studentrole->id, $context);
+        $this->getDataGenerator()->enrol_user($this->user->id, $this->course->id, $this->studentrole->id);
+
+        // Generate a course module.
+        $book = $this->getDataGenerator()->create_module('book', array('course' => $this->course->id));
+
+        // And add a subscription to it.
+        $sub = new stdClass();
+        $sub->courseid = $this->course->id;
+        $sub->userid = $this->user->id;
+        $sub->ruleid = $this->rule->id;
+        $sub->cmid = $book->cmid;
+        $monitorgenerator = $this->getDataGenerator()->get_plugin_generator('tool_monitor');
+        $this->subscription = $monitorgenerator->create_subscription($sub);
+
+        // The subscription should be active to start with. Pass in the id only to refetch the data.
+        $this->assertEquals(true, \tool_monitor\subscription_manager::subscription_is_active($this->subscription));
+
+        // Run the task.
+        $task = new \tool_monitor\task\check_subscriptions();
+        $task->execute();
+
+        // The subscription should still be active. Pass in the id only to refetch the data.
+        $this->reload_subscription();
+        $this->assertEquals(true, \tool_monitor\subscription_manager::subscription_is_active($this->subscription));
+
+        // Make the course module invisible, which should in turn make the subscription inactive.
+        set_coursemodule_visible($book->cmid, false);
+
+        // Run the task.
+        $task = new \tool_monitor\task\check_subscriptions();
+        $task->execute();
+
+        // The subscription should NOT be active. Pass in the id only to refetch the data.
+        $this->reload_subscription();
+        $this->assertEquals(false, \tool_monitor\subscription_manager::subscription_is_active($this->subscription));
+
+        // Make the course module visible again.
+        set_coursemodule_visible($book->cmid, true);
+
+        // Run the task.
+        $task = new \tool_monitor\task\check_subscriptions();
+        $task->execute();
+
+        // The subscription should be active. Pass in the id only to refetch the data.
+        $this->reload_subscription();
+        $this->assertEquals(true, \tool_monitor\subscription_manager::subscription_is_active($this->subscription));
+    }
+
+    /**
+     * Test to confirm that long term inactive subscriptions are removed entirely.
+     */
+    public function test_stale_subscription_removal() {
+        global $DB;
+        // Manually set the inactivedate to 1 day older than the limit allowed.
+        $daysold = 1 + \tool_monitor\subscription_manager::INACTIVE_SUBSCRIPTION_LIFESPAN_IN_DAYS;
+
+        $inactivedate = strtotime("-$daysold days", time());
+        $DB->set_field('tool_monitor_subscriptions', 'inactivedate', $inactivedate, array('id' => $this->subscription->id));
+
+        // Subscription should be inactive to start with.
+        $this->reload_subscription();
+        $this->assertEquals(false, \tool_monitor\subscription_manager::subscription_is_active($this->subscription));
+
+        // Run the task.
+        $task = new \tool_monitor\task\check_subscriptions();
+        $task->execute();
+
+        // Subscription should now not exist at all.
+        $this->assertEquals(false, $DB->record_exists('tool_monitor_subscriptions', array('id' => $this->subscription->id)));
+    }
+
+    /**
+     * Test to confirm that subscriptions for a partially set up user are deactivated.
+     */
+    public function test_user_not_fully_set_up() {
+        global $DB;
+
+        // Enrol the user as a teacher.
+        $this->getDataGenerator()->enrol_user($this->user->id, $this->course->id, $this->teacherrole->id);
+
+        // The subscription should be active to start.
+        $this->assertEquals(true, \tool_monitor\subscription_manager::subscription_is_active($this->subscription));
+
+        // Unset the user's email address, so we fail the check_subscriptions->is_user_setup() call.
+        $DB->set_field('user', 'email', '', array('id' => $this->user->id));
+
+        // Run the task.
+        $task = new \tool_monitor\task\check_subscriptions();
+        $task->execute();
+
+        // The subscription should now be inactive.
+        $this->reload_subscription();
+        $this->assertEquals(false, \tool_monitor\subscription_manager::subscription_is_active($this->subscription));
+    }
+
+    /**
+     * Test to confirm that a suspended user's subscriptions are deactivated properly.
+     */
+    public function test_suspended_user() {
+        global $DB;
+
+        // Enrol the user as a teacher. This role should have the required capability.
+        $this->getDataGenerator()->enrol_user($this->user->id, $this->course->id, $this->teacherrole->id);
+
+        // Subscription should be active to start with.
+        $this->assertEquals(true, \tool_monitor\subscription_manager::subscription_is_active($this->subscription));
+
+        // Suspend the user.
+        $DB->set_field('user', 'suspended', '1', array('id' => $this->user->id));
+
+        // Run the task.
+        $task = new \tool_monitor\task\check_subscriptions();
+        $task->execute();
+
+        // The subscription should now be inactive.
+        $this->reload_subscription();
+        $this->assertEquals(false, \tool_monitor\subscription_manager::subscription_is_active($this->subscription));
+
+        // Unsuspend the user.
+        $DB->set_field('user', 'suspended', '0', array('id' => $this->user->id));
+
+        // Run the task.
+        $task = new \tool_monitor\task\check_subscriptions();
+        $task->execute();
+
+        // The subscription should now be active again.
+        $this->reload_subscription();
+        $this->assertEquals(true, \tool_monitor\subscription_manager::subscription_is_active($this->subscription));
+    }
+}
index c078165..9e5a5a3 100644 (file)
@@ -26,6 +26,6 @@
 
 defined('MOODLE_INTERNAL') || die;
 
-$plugin->version   = 2016052300;     // The current plugin version (Date: YYYYMMDDXX).
+$plugin->version   = 2016052305;     // The current plugin version (Date: YYYYMMDDXX).
 $plugin->requires  = 2016051900;     // Requires this Moodle version.
 $plugin->component = 'tool_monitor'; // Full name of the plugin (used for diagnostics).
index 55ae25d..503f280 100644 (file)
Binary files a/admin/tool/templatelibrary/amd/build/display.min.js and b/admin/tool/templatelibrary/amd/build/display.min.js differ
index 05507ee..66b3fbc 100644 (file)
@@ -120,14 +120,14 @@ define(['jquery', 'core/ajax', 'core/log', 'core/notification', 'core/templates'
 
         var promises = ajax.call([{
             methodname: 'core_output_load_template',
-            args:{
+            args: {
                     component: component,
                     template: name,
                     themename: config.theme
             }
         }, {
             methodname: 'tool_templatelibrary_load_canonical_template',
-            args:{
+            args: {
                     component: component,
                     template: name
             }
@@ -136,13 +136,16 @@ define(['jquery', 'core/ajax', 'core/log', 'core/notification', 'core/templates'
         // When returns a new promise that is resolved when all the passed in promises are resolved.
         // The arguments to the done become the values of each resolved promise.
         $.when.apply($, promises)
-            .done( function(source, originalSource) { templateLoaded(templateName, source, originalSource); })
+            .done(function(source, originalSource) {
+              templateLoaded(templateName, source, originalSource);
+            })
             .fail(notification.exception);
     };
 
     // Add the event listeners.
-    $('[data-region="list-templates"]').on('click', '[data-templatename]', function() {
+    $('[data-region="list-templates"]').on('click', '[data-templatename]', function(e) {
         var templatename = $(this).data('templatename');
+        e.preventDefault();
         loadTemplate(templatename);
     });
 
index 2c9d828..2fabeb2 100644 (file)
@@ -28,11 +28,11 @@ define(['jquery', 'core/ajax', 'core/log', 'core/notification', 'core/templates'
      * The ajax call has returned with a new list of templates.
      *
      * @method reloadListTemplate
-     * @param String[] templates List of template ids.
+     * @param {String[]} templateList List of template ids.
      */
     var reloadListTemplate = function(templateList) {
         templates.render('tool_templatelibrary/search_results', { templates: templateList })
-            .done(function (result, js) {
+            .done(function(result, js) {
                 templates.replaceNode($('[data-region="searchresults"]'), result, js);
             }).fail(notification.exception);
     };
@@ -63,8 +63,8 @@ define(['jquery', 'core/ajax', 'core/log', 'core/notification', 'core/templates'
      * the function will only be executed once.
      *
      * @method queueRefresh
-     * @param function callback
-     * @param int delay The time in milliseconds to delay.
+     * @param {function} callback
+     * @param {Number} delay The time in milliseconds to delay.
      */
     var queueRefresh = function(callback, delay) {
         if (throttle !== null) {
index 5900d32..99d0ce5 100644 (file)
@@ -122,7 +122,7 @@ class edit_field extends XMLDBAction {
             $o.= '      <input type="hidden" name ="name" value="' .  s($field->getName()) .'" />';
             $o.= '      <tr valign="top"><td>Name:</td><td colspan="2">' . s($field->getName()) . '</td></tr>';
         } else {
-            $o.= '      <tr valign="top"><td><label for="name" accesskey="n">Name:</label></td><td colspan="2"><input name="name" type="text" size="30" maxlength="30" id="name" value="' . s($field->getName()) . '" /></td></tr>';
+            $o.= '      <tr valign="top"><td><label for="name" accesskey="n">Name:</label></td><td colspan="2"><input name="name" type="text" size="'.xmldb_field::NAME_MAX_LENGTH.'" maxlength="'.xmldb_field::NAME_MAX_LENGTH.'" id="name" value="' . s($field->getName()) . '" /></td></tr>';
         }
         // XMLDB field comment
         $o.= '      <tr valign="top"><td><label for="comment" accesskey="c">Comment:</label></td><td colspan="2"><textarea name="comment" rows="3" cols="80" id="comment">' . s($field->getComment()) . '</textarea></td></tr>';
index 7725c9e..a72b1c5 100644 (file)
@@ -113,7 +113,7 @@ class edit_index extends XMLDBAction {
         if ($structure->getIndexUses($table->getName(), $index->getName())) {
             $disabled = ' disabled="disabled " ';
         }
-        $o.= '      <tr valign="top"><td><label for="name" accesskey="n">Name:</label></td><td colspan="2"><input name="name" type="text" size="30" id="name"' . $disabled . ' value="' . s($index->getName()) . '" /></td></tr>';
+        $o.= '      <tr valign="top"><td><label for="name" accesskey="n">Name:</label></td><td colspan="2"><input name="name" type="text" size="'.xmldb_field::NAME_MAX_LENGTH.'" id="name"' . $disabled . ' value="' . s($index->getName()) . '" /></td></tr>';
         // XMLDB key comment
         $o.= '      <tr valign="top"><td><label for="comment" accesskey="c">Comment:</label></td><td colspan="2"><textarea name="comment" rows="3" cols="80" id="comment">' . s($index->getComment()) . '</textarea></td></tr>';
         // xmldb_index Type
index c1d1c3b..6e32acf 100644 (file)
@@ -113,7 +113,7 @@ class edit_key extends XMLDBAction {
         if ($structure->getKeyUses($table->getName(), $key->getName())) {
             $disabled = ' disabled="disabled " ';
         }
-        $o.= '      <tr valign="top"><td><label for="name" accesskey="n">Name:</label></td><td colspan="2"><input name="name" type="text" size="30" id="name"' . $disabled . ' value="' . s($key->getName()) . '" /></td></tr>';
+        $o.= '      <tr valign="top"><td><label for="name" accesskey="n">Name:</label></td><td colspan="2"><input name="name" type="text" size="'.xmldb_field::NAME_MAX_LENGTH.'" id="name"' . $disabled . ' value="' . s($key->getName()) . '" /></td></tr>';
         // XMLDB key comment
         $o.= '      <tr valign="top"><td><label for="comment" accesskey="c">Comment:</label></td><td colspan="2"><textarea name="comment" rows="3" cols="80" id="comment">' . s($key->getComment()) . '</textarea></td></tr>';
         // xmldb_key Type
index 4199a6c..5c2e7b9 100644 (file)
@@ -129,7 +129,7 @@ class edit_table extends XMLDBAction {
         if ($structure->getTableUses($table->getName())) {
             $o.= '      <tr valign="top"><td>Name:</td><td><input type="hidden" name ="name" value="' . s($table->getName()) . '" />' . s($table->getName()) .'</td></tr>';
         } else {
-            $o.= '      <tr valign="top"><td><label for="name" accesskey="p">Name:</label></td><td><input name="name" type="text" size="28" maxlength="28" id="name" value="' . s($table->getName()) . '" /></td></tr>';
+            $o.= '      <tr valign="top"><td><label for="name" accesskey="p">Name:</label></td><td><input name="name" type="text" size="'.xmldb_table::NAME_MAX_LENGTH.'" maxlength="'.xmldb_table::NAME_MAX_LENGTH.'" id="name" value="' . s($table->getName()) . '" /></td></tr>';
         }
         $o.= '      <tr valign="top"><td><label for="comment" accesskey="c">Comment:</label></td><td><textarea name="comment" rows="3" cols="80" id="comment">' . s($table->getComment()) . '</textarea></td></tr>';
         $o.= '      <tr valign="top"><td>&nbsp;</td><td><input type="submit" value="' .$this->str['change'] . '" /></td></tr>';
index 0211abd..8264677 100644 (file)
@@ -7,14 +7,11 @@
 #page-blocks-community-communitycourse .hubtext {display: block; width: 68%; padding-left: 165px;}
 #page-blocks-community-communitycourse .hubimgandtext {display:table;}
 #page-blocks-community-communitycourse .hubimage {float: left; display: block; width: 100px;}
-#page-blocks-community-communitycourse .hubdescriptiontext {}
 #page-blocks-community-communitycourse .hubstats {padding-top: 10px}
 #page-blocks-community-communitycourse .hubstats .iconhelp {float: left; padding-right: 3px;}
 #page-blocks-community-communitycourse .hubadditionaldesc {color: #666666; font-size: 90%; display:block;}
 #page-blocks-community-communitycourse .hubscreenshot {margin-right: 10px;}
-#page-blocks-community-communitycourse .hubnottrusted {}
 #page-blocks-community-communitycourse .hubtrusted {display:inline;}
-#page-blocks-community-communitycourse .hubnottrusted {}
 #page-blocks-community-communitycourse .trustedtr {background-color: #ffe1c3;}
 #page-blocks-community-communitycourse .prioritisetr {background-color: #ffd4ff;}
 #page-blocks-community-communitycourse .blockdescription {font-size: 80%; color: #555555;}
@@ -25,7 +22,6 @@
 #page-blocks-community-communitycourse .comment-link {font-size: 80%; color: #555555;}
 #page-blocks-community-communitycourse .coursescreenshot {text-align:center;cursor: pointer;}
 #page-blocks-community-communitycourse .hubcourseinfo {margin-left: 15px;}
-#page-blocks-community-communitycourse .coursesitelink {}
 #page-blocks-community-communitycourse .pagingbar {text-align: center;}
 #page-blocks-community-communitycourse .coursecomment {float: right;}
 #page-blocks-community-communitycourse .courseoperations {margin-top:9px; text-align:center}
     font-size: 95%;
     margin-bottom: 9px;
 }
-#page-blocks-community-communitycourse .comment-list li {
-    background-color:#FFFAFA !important;
-    -moz-border-radius: 6px;
-    -webkit-border-radius: 6px;
-    padding-right: 4px;
-    padding-bottom: 2px;
-}
 
 /*  STAR RATING  */
 #page-blocks-community-communitycourse .ratingcount {color: #8B8989;font-size: 80%;vertical-align: top;}
     margin-top:15px;
 }
 #page-blocks-community-communitycourse .hubrateandcomment { font-size: 80%;}
-#page-blocks-community-communitycourse .hubcourseoutcomes {}
 #page-blocks-community-communitycourse .nextlink {text-align: center;margin-top: 6px;}
 #page-blocks-community-communitycourse .textinfo { text-align: center;}
 
index 41b9b45..d935742 100644 (file)
@@ -28,9 +28,9 @@ define(['jquery'], function($) {
     // Copied from lib/navigationlib.php navigation_node constants.
     var NODETYPE = {
         // @type int Activity (course module) = 40.
-        ACTIVITY : 40,
+        ACTIVITY: 40,
         // @type int Resource (course module = 50.
-        RESOURCE : 50,
+        RESOURCE: 50,
     };
 
     /**
@@ -39,7 +39,6 @@ define(['jquery'], function($) {
      * @method buildDOM
      * @param {Object} rootElement the root element of DOM.
      * @param {object} nodes jquery object representing the nodes to be build.
-     * @return
      */
     function buildDOM(rootElement, nodes) {
         var ul = $('<ul></ul>');
@@ -94,7 +93,7 @@ define(['jquery'], function($) {
 
                 if (icon) {
                     link.append(icon);
-                    link.append('<span class="item-content-wrap">'+node.name+'</span>');
+                    link.append('<span class="item-content-wrap">' + node.name + '</span>');
                 } else {
                     link.append(node.name);
                 }
@@ -109,7 +108,7 @@ define(['jquery'], function($) {
 
                 if (icon) {
                     span.append(icon);
-                    span.append('<span class="item-content-wrap">'+node.name+'</span>');
+                    span.append('<span class="item-content-wrap">' + node.name + '</span>');
                 } else {
                     span.append(node.name);
                 }
index 2cb5137..ce5cb99 100644 (file)
@@ -29,8 +29,8 @@ define(['jquery', 'core/ajax', 'core/config', 'block_navigation/ajax_response_re
          * Get the block instance id.
          *
          * @function getBlockInstanceId
-         * @param element
-         * @returns {*}
+         * @param {Element} element
+         * @returns {String} the instance id
          */
         function getBlockInstanceId(element) {
             return element.closest('[data-block]').attr('data-instanceid');
index 976ae98..990cd96 100644 (file)
@@ -1,4 +1,4 @@
-@block @block_private_files @file_upload @javascript
+@block @block_private_files @_file_upload @javascript
 Feature: The private files block allows users to store files privately in moodle
   In order to store a private file in moodle
   As a user
index 77d5756..ec611bb 100644 (file)
@@ -1,4 +1,4 @@
-@block @block_private_files @file_upload
+@block @block_private_files @_file_upload
 Feature: The private files block allows users to store files privately in moodle
   In order to store a private file in moodle
   As a teacher
index 7df025c..42b3d42 100644 (file)
@@ -153,7 +153,11 @@ class comment {
         } else if (!empty($options->courseid)) {
             $this->courseid = $options->courseid;
         } else {
-            $this->courseid = SITEID;
+            if ($coursecontext = $this->context->get_course_context(false)) {
+                $this->courseid = $coursecontext->instanceid;
+            } else {
+                $this->courseid = SITEID;
+            }
         }
 
         // setup coursemodule
index b11daa4..a440bc2 100644 (file)
@@ -4152,7 +4152,7 @@ class api {
      * @param int $limit Number of records to return.
      * @return \core_competency\evidence[]
      */
-    public static function list_evidence_in_course($userid = 0, $courseid = 0, $competencyid = 0, $sort = 'timecreated',
+    public static function list_evidence_in_course($userid = 0, $courseid = 0, $competencyid = 0, $sort = 'timecreated, id',
                                                    $order = 'DESC', $skip = 0, $limit = 0) {
         static::require_enabled();
 
@@ -4166,11 +4166,8 @@ class api {
             return array();
         }
 
-        $params = array(
-            'usercompetencyid' => $usercompetency->get_id(),
-            'contextid' => context_course::instance($courseid)->id
-        );
-        return evidence::get_records($params, $sort, $order, $skip, $limit);
+        $context = context_course::instance($courseid);
+        return evidence::get_records_for_usercompetency($usercompetency->get_id(), $context, $sort, $order, $skip, $limit);
     }
 
     /**
index 79a8cc5..1e12a36 100644 (file)
@@ -88,7 +88,7 @@ class course_competency_settings extends persistent {
     public static function can_read($courseid) {
         $context = context_course::instance($courseid);
 
-        $capabilities = array('moodle/competency:coursecompetencyview');
+        $capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage');
 
         return has_any_capability($capabilities, $context);
     }
index ed3445d..cc8ec05 100644 (file)
@@ -284,4 +284,52 @@ class evidence extends persistent {
         return has_capability('moodle/competency:evidencedelete', context_user::instance($userid));
     }
 
+    /**
+     * Load a list of records in a context for a user competency.
+     *
+     * @param int $usercompetencyid The id of the user competency.
+     * @param context $context Context to filter the evidence list.
+     * @param string $sort The field from the evidence table to sort on.
+     * @param string $order The sort direction
+     * @param int $skip Limitstart.
+     * @param int $limit Number of rows to return.
+     *
+     * @return \core_competency\persistent[]
+     */
+    public static function get_records_for_usercompetency($usercompetencyid,
+                                                          \context $context,
+                                                          $sort = '',
+                                                          $order = 'ASC',
+                                                          $skip = 0,
+                                                          $limit = 0) {
+        global $DB;
+
+        $params = array(
+            'usercompid' => $usercompetencyid,
+            'path' => $context->path . '/%',
+            'contextid' => $context->id
+        );
+
+        if (!empty($sort)) {
+            $sortcolumns = explode(',', $sort);
+            $sortcolumns = array_map('trim', $sortcolumns);
+            $sort = ' ORDER BY e.' . implode(', e.', $sortcolumns) . ' ' . $order;
+        }
+
+        $sql = 'SELECT e.*
+                  FROM {' . static::TABLE . '} e
+                  JOIN {context} c ON c.id = e.contextid
+                 WHERE (c.path LIKE :path OR c.id = :contextid)
+                   AND e.usercompetencyid = :usercompid
+                 ' . $sort;
+        $records = $DB->get_records_sql($sql, $params, $skip, $limit);
+        $instances = array();
+
+        foreach ($records as $record) {
+            $newrecord = new static(0, $record);
+            array_push($instances, $newrecord);
+        }
+        return $instances;
+    }
+
 }
index 9e5c4d3..44aaf76 100644 (file)
@@ -2579,6 +2579,47 @@ class core_competency_api_testcase extends advanced_testcase {
         $this->assertEquals(null, $ev4->get_actionuserid());
     }
 
+    public function test_list_evidence_in_course() {
+        global $SITE;
+
+        $this->resetAfterTest(true);
+        $dg = $this->getDataGenerator();
+        $lpg = $dg->get_plugin_generator('core_competency');
+        $u1 = $dg->create_user();
+        $course = $dg->create_course();
+        $coursecontext = context_course::instance($course->id);
+
+        $this->setAdminUser();
+        $f = $lpg->create_framework();
+        $c = $lpg->create_competency(array('competencyframeworkid' => $f->get_id()));
+        $c2 = $lpg->create_competency(array('competencyframeworkid' => $f->get_id()));
+        $cc = api::add_competency_to_course($course->id, $c->get_id());
+        $cc2 = api::add_competency_to_course($course->id, $c2->get_id());
+
+        $pagegenerator = $this->getDataGenerator()->get_plugin_generator('mod_page');
+        $page = $pagegenerator->create_instance(array('course' => $course->id));
+
+        $cm = get_coursemodule_from_instance('page', $page->id);
+        $cmcontext = context_module::instance($cm->id);
+        // Add the competency to the course module.
+        $ccm = api::add_competency_to_course_module($cm, $c->get_id());
+
+        // Now add the evidence to the course.
+        $evidence1 = api::add_evidence($u1->id, $c->get_id(), $coursecontext->id, \core_competency\evidence::ACTION_LOG,
+            'invaliddata', 'error');
+
+        $result = api::list_evidence_in_course($u1->id, $course->id, $c->get_id());
+        $this->assertEquals($result[0]->get_id(), $evidence1->get_id());
+
+        // Now add the evidence to the course module.
+        $evidence2 = api::add_evidence($u1->id, $c->get_id(), $cmcontext->id, \core_competency\evidence::ACTION_LOG,
+            'invaliddata', 'error');
+
+        $result = api::list_evidence_in_course($u1->id, $course->id, $c->get_id());
+        $this->assertEquals($result[0]->get_id(), $evidence2->get_id());
+        $this->assertEquals($result[1]->get_id(), $evidence1->get_id());
+    }
+
     public function test_list_course_modules_using_competency() {
         global $SITE;
 
index 05eda15..28437de 100644 (file)
@@ -61,10 +61,11 @@ class completion_criteria_activity extends completion_criteria {
      */
     public function config_form_display(&$mform, $data = null) {
         $modnames = get_module_types_names();
-        $mform->addElement('checkbox', 'criteria_activity['.$data->id.']',
-                $modnames[self::get_mod_name($data->module)].
-                ' - '.
-                format_string($data->name));
+        $mform->addElement('advcheckbox',
+                'criteria_activity['.$data->id.']',
+                $modnames[self::get_mod_name($data->module)] . ' - ' . format_string($data->name),
+                null,
+                array('group' => 1));
 
         if ($this->id) {
             $mform->setDefault('criteria_activity['.$data->id.']', 1);
index a96f802..73e59f1 100644 (file)
@@ -1,7 +1,12 @@
 {
+    "name": "moodle/moodle",
+    "license": "GPL-3.0",
+    "description": "Moodle - the world's open source learning platform",
+    "type": "project",
+    "homepage": "https://moodle.org",
     "require-dev": {
         "phpunit/phpunit": "4.8.*",
         "phpunit/dbUnit": "1.4.*",
-        "moodlehq/behat-extension": "3.32.0"
+        "moodlehq/behat-extension": "3.32.1"
     }
 }
index f6f23ac..4eda2d9 100644 (file)
@@ -4,8 +4,8 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
         "This file is @generated automatically"
     ],
-    "hash": "0bd3021c5dea5e28a07d7a58d0f1676d",
-    "content-hash": "528abab2d8628a8442924ec3defc561d",
+    "hash": "949f8a407958a19e2dba7929b3dc0576",
+    "content-hash": "bd742592f8ed4700884f6c651226c961",
     "packages": [],
     "packages-dev": [
         {
         },
         {
             "name": "moodlehq/behat-extension",
-            "version": "v3.32.0",
+            "version": "v3.32.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/moodlehq/moodle-behat-extension.git",
-                "reference": "f0b6a44de9111fd4fa82796aca712b9e9772d07e"
+                "reference": "f8305058ce9140864c23c9b667e3d7d487fdc006"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/moodlehq/moodle-behat-extension/zipball/f0b6a44de9111fd4fa82796aca712b9e9772d07e",
-                "reference": "f0b6a44de9111fd4fa82796aca712b9e9772d07e",
+                "url": "https://api.github.com/repos/moodlehq/moodle-behat-extension/zipball/f8305058ce9140864c23c9b667e3d7d487fdc006",
+                "reference": "f8305058ce9140864c23c9b667e3d7d487fdc006",
                 "shasum": ""
             },
             "require": {
                 "Behat",
                 "moodle"
             ],
-            "time": "2016-05-09 03:32:06"
+            "time": "2016-06-20 07:56:08"
         },
         {
             "name": "phpdocumentor/reflection-docblock",
         },
         {
             "name": "sebastian/exporter",
-            "version": "1.2.1",
+            "version": "1.2.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/exporter.git",
-                "reference": "7ae5513327cb536431847bcc0c10edba2701064e"
+                "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/7ae5513327cb536431847bcc0c10edba2701064e",
-                "reference": "7ae5513327cb536431847bcc0c10edba2701064e",
+                "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/42c4c2eec485ee3e159ec9884f95b431287edde4",
+                "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4",
                 "shasum": ""
             },
             "require": {
                 "sebastian/recursion-context": "~1.0"
             },
             "require-dev": {
+                "ext-mbstring": "*",
                 "phpunit/phpunit": "~4.4"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.2.x-dev"
+                    "dev-master": "1.3.x-dev"
                 }
             },
             "autoload": {
                 "export",
                 "exporter"
             ],
-            "time": "2015-06-21 07:55:53"
+            "time": "2016-06-17 09:04:28"
         },
         {
             "name": "sebastian/global-state",
index ce4880a..53d4b47 100644 (file)
@@ -88,6 +88,9 @@ class course_completion_form extends moodleform {
         $activities = $completion->get_activities();
         if (!empty($activities)) {
 
+            if (!$completion->is_course_locked()) {
+                $this->add_checkbox_controller(1, null, null, 0);
+            }
             foreach ($activities as $activity) {
                 $params_a = array('moduleinstance' => $activity->id);
                 $criteria = new completion_criteria_activity(array_merge($params, $params_a));
index b348aa1..c43f1cd 100644 (file)
@@ -195,9 +195,12 @@ class core_course_external extends external_api {
                 $sectionvalues['id'] = $section->id;
                 $sectionvalues['name'] = get_section_name($course, $section);
                 $sectionvalues['visible'] = $section->visible;
+
+                $options = (object) array('noclean' => true);
                 list($sectionvalues['summary'], $sectionvalues['summaryformat']) =
                         external_format_text($section->summary, $section->summaryformat,
-                                $context->id, 'course', 'section', $section->id);
+                                $context->id, 'course', 'section', $section->id, $options);
+                $sectionvalues['section'] = $section->section;
                 $sectioncontents = array();
 
                 //for each module of the section
@@ -325,6 +328,7 @@ class core_course_external extends external_api {
                     'visible' => new external_value(PARAM_INT, 'is the section visible', VALUE_OPTIONAL),
                     'summary' => new external_value(PARAM_RAW, 'Section description'),
                     'summaryformat' => new external_format_value('summary'),
+                    'section' => new external_value(PARAM_INT, 'Section number inside the course', VALUE_OPTIONAL),
                     'modules' => new external_multiple_structure(
                             new external_single_structure(
                                 array(
index 9bb62ab..6705b5b 100644 (file)
@@ -139,8 +139,3 @@ Optional file (styles)
 
   If this file exists it will be included in the CSS Moodle generates.
 
-
-Optional delete course hook
----------------------------
-
-* in your yourformat/lib.php add function format_yourformat_delete_course($courseid)
\ No newline at end of file
index 1e6a7dd..03db896 100644 (file)
@@ -41,18 +41,14 @@ Feature: Sections can be edited and deleted in topics format
     Then I should see "Use default section name [Topic 2]"
 
   Scenario: Edit section summary in topics format
-    When I edit the section "2"
-    And I set the following fields to these values:
+    When I edit the section "2" and I fill the form with:
       | Summary | Welcome to section 2 |
-    And I press "Save changes"
     Then I should see "Welcome to section 2" in the "li#section-2" "css_element"
 
   Scenario: Edit section default name in topics format
-    When I edit the section "2"
-    And I set the following fields to these values:
+    When I edit the section "2" and I fill the form with:
       | Use default section name | 0                        |
       | name                     | This is the second topic |
-    And I press "Save changes"
     Then I should see "This is the second topic" in the "li#section-2" "css_element"
     And I should not see "Topic 2" in the "li#section-2" "css_element"
 
index 8d82f27..8bdcfe0 100644 (file)
@@ -2,6 +2,9 @@ This files describes API changes for course formats
 
 Overview of this plugin type at http://docs.moodle.org/dev/Course_formats
 
+=== 3.2 ===
+* Callback delete_course is deprecated and should be replaced with observer for event \core\event\course_content_deleted
+
 === 3.1 ===
 * Course format may use the inplace_editable template to allow quick editing of section names, see
   https://docs.moodle.org/dev/Inplace_editable and MDL-51802 for example implementation.
index 787866a..637a328 100644 (file)
@@ -58,7 +58,6 @@ class core_course_renderer extends plugin_renderer_base {
     public function __construct(moodle_page $page, $target) {
         $this->strings = new stdClass;
         parent::__construct($page, $target);
-        $this->add_modchoosertoggle();
     }
 
     /**
@@ -66,8 +65,12 @@ class core_course_renderer extends plugin_renderer_base {
      *
      * Theme can overwrite as an empty function to exclude it (for example if theme does not
      * use modchooser at all)
+     *
+     * @deprecated since 3.2
      */
     protected function add_modchoosertoggle() {
+        debugging('core_course_renderer::add_modchoosertoggle() is deprecated.', DEBUG_DEVELOPER);
+
         global $CFG;
 
         // Only needs to be done once per page.
@@ -182,9 +185,7 @@ class core_course_renderer extends plugin_renderer_base {
         array(array('courseid' => $course->id, 'closeButtonTitle' => get_string('close', 'editor')))
         );
         $this->page->requires->strings_for_js(array(
-                'addresourceoractivity',
-                'modchooserenable',
-                'modchooserdisable',
+                'addresourceoractivity'
         ), 'moodle');
 
         // Add the header
@@ -406,9 +407,6 @@ class core_course_renderer extends plugin_renderer_base {
     /**
      * Renders HTML for the menus to add activities and resources to the current course
      *
-     * Note, if theme overwrites this function and it does not use modchooser,
-     * see also {@link core_course_renderer::add_modchoosertoggle()}
-     *
      * @param stdClass $course
      * @param int $section relative section number (field course_sections.section)
      * @param int $sectionreturn The section to link back to
index ed78f98..cf006b9 100644 (file)
@@ -401,7 +401,7 @@ class behat_course extends behat_base {
     public function i_edit_the_section_and_i_fill_the_form_with($sectionnumber, TableNode $data) {
 
         // Edit given section.
-        $this->execute("behat_course::i_edit_the_section");
+        $this->execute("behat_course::i_edit_the_section", $sectionnumber);
 
         // Set form fields.
         $this->execute("behat_forms::i_set_the_following_fields_to_these_values", $data);
index 6fc0568..6d8485d 100644 (file)
@@ -43,6 +43,5 @@ Feature: Restrict activities availability
     And I log in as "teacher1"
     And I follow "Course 1"
     When I turn editing mode on
-    And I follow "Activity chooser off"
     Then the "Add an activity to section 'Topic 1'" select box should not contain "Chat"
     Then the "Add an activity to section 'Topic 1'" select box should not contain "Glossary"
index 9ad5d73..8ec184b 100644 (file)
@@ -686,6 +686,7 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
      * @return array A list with the course object and course modules objects
      */
     private function prepare_get_course_contents_test() {
+        global $DB;
         $course  = self::getDataGenerator()->create_course();
         $forumdescription = 'This is the forum description';
         $forum = $this->getDataGenerator()->create_module('forum',
@@ -710,6 +711,10 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
         $roleid = $this->assignUserCapability('moodle/course:view', $context->id);
         $this->assignUserCapability('moodle/course:update', $context->id, $roleid);
 
+        $conditions = array('course' => $course->id, 'section' => 2);
+        $DB->set_field('course_sections', 'summary', 'Text with iframe <iframe src="https://moodle.org"></iframe>', $conditions);
+        rebuild_course_cache($course->id, true);
+
         return array($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm);
     }
 
@@ -749,10 +754,14 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
             }
         }
         $this->assertEquals(2, $testexecuted);
+        $this->assertEquals(0, $firstsection['section']);
 
         // Check that the only return section has the 5 created modules.
         $this->assertCount(4, $firstsection['modules']);
         $this->assertCount(1, $lastsection['modules']);
+        $this->assertEquals(2, $lastsection['section']);
+        $this->assertContains('<iframe', $lastsection['summary']);
+        $this->assertContains('</iframe>', $lastsection['summary']);
 
         try {
             $sections = core_course_external::get_course_contents($course->id,
diff --git a/course/upgrade.txt b/course/upgrade.txt
new file mode 100644 (file)
index 0000000..8e73dd9
--- /dev/null
@@ -0,0 +1,7 @@
+This files describes API changes in /course/*,
+information provided here is intended especially for developers.
+
+=== 3.2 ===
+
+ * External function core_course_external::get_course_contents now returns the section's number in the course (new section field).
+
index d4d67cf..2bd50f0 100644 (file)
@@ -17,7 +17,6 @@
     $move        = optional_param('move', 0, PARAM_INT);
     $marker      = optional_param('marker',-1 , PARAM_INT);
     $switchrole  = optional_param('switchrole',-1, PARAM_INT); // Deprecated, use course/switchrole.php instead.
-    $modchooser  = optional_param('modchooser', -1, PARAM_BOOL);
     $return      = optional_param('return', 0, PARAM_LOCALURL);
 
     $params = array();
                 redirect($PAGE->url);
             }
         }
-        if (($modchooser == 1) && confirm_sesskey()) {
-            set_user_preference('usemodchooser', $modchooser);
-        } else if (($modchooser == 0) && confirm_sesskey()) {
-            set_user_preference('usemodchooser', $modchooser);
-        }
 
         if (has_capability('moodle/course:sectionvisibility', $context)) {
             if ($hide && confirm_sesskey()) {
index 1689ac9..b986555 100644 (file)
Binary files a/course/yui/build/moodle-course-modchooser/moodle-course-modchooser-debug.js and b/course/yui/build/moodle-course-modchooser/moodle-course-modchooser-debug.js differ
index 1f08287..a690c8a 100644 (file)
Binary files a/course/yui/build/moodle-course-modchooser/moodle-course-modchooser-min.js and b/course/yui/build/moodle-course-modchooser/moodle-course-modchooser-min.js differ
index 1689ac9..b986555 100644 (file)
Binary files a/course/yui/build/moodle-course-modchooser/moodle-course-modchooser.js and b/course/yui/build/moodle-course-modchooser/moodle-course-modchooser.js differ
index ef6ac4d..8adbac4 100644 (file)
@@ -54,9 +54,6 @@ Y.extend(MODCHOOSER, M.core.chooserdialogue, {
         // Initialize existing sections and register for dynamically created sections
         this.setup_for_section();
         M.course.coursebase.register_module(this);
-
-        // Catch the page toggle
-        Y.all('.block_settings #settingsnav .type_course .modchoosertoggle a').on('click', this.toggle_mod_chooser, this);
     },
 
     /**
@@ -130,62 +127,6 @@ Y.extend(MODCHOOSER, M.core.chooserdialogue, {
         this.display_chooser(e);
     },
 
-    /**
-     * Toggle availability of the activity chooser.
-     *
-     * @method toggle_mod_chooser
-     * @param {EventFacade} e
-     */
-    toggle_mod_chooser : function(e) {
-        // Get the add section link
-        var modchooserlinks = Y.all('div.addresourcemodchooser');
-
-        // Get the dropdowns
-        var dropdowns = Y.all('div.addresourcedropdown');
-
-        if (modchooserlinks.size() === 0) {
-            // Continue with non-js action if there are no modchoosers to add
-            return;
-        }
-
-        // We need to update the text and link
-        var togglelink = Y.one('.block_settings #settingsnav .type_course .modchoosertoggle a');
-
-        // The actual text is in the last child
-        var toggletext = togglelink.get('lastChild');
-
-        var usemodchooser;
-        // Determine whether they're currently hidden
-        if (modchooserlinks.item(0).hasClass('visibleifjs')) {
-            // The modchooser is currently visible, hide it
-            usemodchooser = 0;
-            modchooserlinks
-                .removeClass('visibleifjs')
-                .addClass('hiddenifjs');
-            dropdowns
-                .addClass('visibleifjs')
-                .removeClass('hiddenifjs');
-            toggletext.set('data', M.util.get_string('modchooserenable', 'moodle'));
-            togglelink.set('href', togglelink.get('href').replace('off', 'on'));
-        } else {
-            // The modchooser is currently not visible, show it
-            usemodchooser = 1;
-            modchooserlinks
-                .addClass('visibleifjs')
-                .removeClass('hiddenifjs');
-            dropdowns
-                .removeClass('visibleifjs')
-                .addClass('hiddenifjs');
-            toggletext.set('data', M.util.get_string('modchooserdisable', 'moodle'));
-            togglelink.set('href', togglelink.get('href').replace('on', 'off'));
-        }
-
-        M.util.set_user_preference('usemodchooser', usemodchooser);
-
-        // Prevent the page from reloading
-        e.preventDefault();
-    },
-
     /**
      * Helper function to set the value of a hidden radio button when a
      * selection is made.
index d2d6336..83a9b13 100644 (file)
@@ -202,8 +202,10 @@ class helper {
             'connecttimeout' => 5
         );
 
-        if (!$iconfiles = $fs->create_file_from_url($filerecord, $url, $urlparams)) {
-            return self::PROFILE_IMAGE_UPDATE_FAILED;
+        try {
+            $fs->create_file_from_url($filerecord, $url, $urlparams);
+        } catch (\file_exception $e) {
+            return get_string($e->errorcode, $e->module, $e->a);
         }
 
         $iconfile = $fs->get_area_files($context->id, 'user', 'newicon', false, 'itemid', false);
index 4b541cd..2818430 100644 (file)
@@ -58,6 +58,9 @@ class behat_enrol extends behat_base {
             array($this->escape($enrolmethod), get_string('addinstance', 'enrol'))
         );
 
+        // Wait again, for page to reloaded.
+        $this->execute('behat_general::i_wait_to_be_redirected');
+
         // Set form fields.
         $this->execute("behat_forms::i_set_the_following_fields_to_these_values", $table);
 
index a1283ce..f46f5f3 100644 (file)
@@ -1,14 +1,26 @@
 /**
  * Filters
  */
-.mediaplugin_html5audio, .mediaplugin_html5video, .mediaplugin_swf, .mediaplugin_flv, .mediaplugin_real, .mediaplugin_youtube, .mediaplugin_vimeo, .mediaplugin_wmp, .mediaplugin_qt
-    {display:block;margin-top:5px;margin-bottom:5px;text-align: center;}
-.mediaplugin.mediaplugin_mp3 object {display:inline;height:15px;width:180px;margin-left:0.5em;}
-
+.mediaplugin_html5audio,
+.mediaplugin_html5video,
+.mediaplugin_swf,
+.mediaplugin_flv,
+.mediaplugin_real,
+.mediaplugin_youtube,
+.mediaplugin_vimeo,
+.mediaplugin_wmp,
+.mediaplugin_qt {
+  display: block;
+  margin-top: 5px;
+  margin-bottom: 5px;
+  text-align: center;
+}
 
 /*
  * mp3 player colours -this read using JS and applied to swf audio flow player
  * see http://flowplayer.org/documentation/skinning/controlbar.html?skin=default for more color properties,
  * any property that ends with '...Color' is supported here.
  */
-.mp3flowplayer_backgroundColor {color: #000000;}
+.mp3flowplayer_backgroundColor {
+  color: #000000;
+}
index 6f9b158..09ffb0c 100644 (file)
@@ -22,7 +22,7 @@
  * @copyright  2015 Jun Pataleta <jun@moodle.com>
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
   */
-define(['jquery', 'core/templates', 'core/notification', 'core/yui'], function ($, templates, notification) {
+define(['jquery', 'core/templates', 'core/notification', 'core/yui'], function($, templates, notification) {
 
     // Private variables and functions.
 
@@ -34,18 +34,18 @@ define(['jquery', 'core/templates', 'core/notification', 'core/yui'], function (
          * Basically, it performs the binding and handling of the button click event for
          * the 'Insert frequently used comment' button.
          *
-         * @param criterionId The criterion ID.
-         * @param buttonId The element ID of the button which the handler will be bound to.
-         * @param remarkId The element ID of the remark text area where the text of the selected comment will be copied to.
-         * @param commentOptions The array of frequently used comments to be used as options.
+         * @param {Integer} criterionId The criterion ID.
+         * @param {String} buttonId The element ID of the button which the handler will be bound to.
+         * @param {String} remarkId The element ID of the remark text area where the text of the selected comment will be copied to.
+         * @param {Array} commentOptions The array of frequently used comments to be used as options.
          */
-        initialise: function (criterionId, buttonId, remarkId, commentOptions) {
+        initialise: function(criterionId, buttonId, remarkId, commentOptions) {
             /**
              * Display the chooser dialog using the compiled HTML from the mustache template
              * and binds onclick events for the generated comment options.
              *
-             * @param compiledSource The compiled HTML from the mustache template
-             * @param comments Array containing comments.
+             * @param {String} compiledSource The compiled HTML from the mustache template
+             * @param {Array} comments Array containing comments.
              */
             function displayChooserDialog(compiledSource, comments) {
                 var titleLabel = '<label>' + M.util.get_string('insertcomment', 'gradingform_guide') + '</label>';
@@ -68,11 +68,11 @@ define(['jquery', 'core/templates', 'core/notification', 'core/yui'], function (
                 });
 
                 // Loop over each comment item and bind click events.
-                $.each(comments, function (index, comment) {
+                $.each(comments, function(index, comment) {
                     var commentOptionId = '#comment-option-' + criterionId + '-' + comment.id;
 
                     // Delegate click event for the generated option link.
-                    $(commentOptionId).click(function () {
+                    $(commentOptionId).click(function() {
                         var remarkTextArea = $('#' + remarkId);
                         var remarkText = remarkTextArea.val();
 
@@ -88,7 +88,7 @@ define(['jquery', 'core/templates', 'core/notification', 'core/yui'], function (
                     });
 
                     // Handle keypress on list items.
-                    $(document).off('keypress', commentOptionId).on('keypress', commentOptionId, function () {
+                    $(document).off('keypress', commentOptionId).on('keypress', commentOptionId, function() {
                         var keyCode = event.which || event.keyCode;
 
                         // Enter or space key.
@@ -124,14 +124,14 @@ define(['jquery', 'core/templates', 'core/notification', 'core/yui'], function (
 
                 // Render the template and display the comment chooser dialog.
                 templates.render('gradingform_guide/comment_chooser', context)
-                    .done(function (compiledSource) {
+                    .done(function(compiledSource) {
                         displayChooserDialog(compiledSource, commentOptions);
                     })
                     .fail(notification.exception);
             }
 
             // Bind click event for the comments chooser button.
-            $("#" + buttonId).click(function (e) {
+            $("#" + buttonId).click(function(e) {
                 e.preventDefault();
                 generateCommentsChooser();
             });
index 71e2c5d..ed2ae2d 100644 (file)
@@ -70,7 +70,7 @@ $string['guidestatus'] = 'Current marking guide status';
 $string['hidemarkerdesc'] = 'Hide marker criterion descriptions';
 $string['hidestudentdesc'] = 'Hide student criterion descriptions';
 $string['insertcomment'] = 'Insert frequently used comment';
-$string['maxscore'] = 'Maximum mark';
+$string['maxscore'] = 'Maximum score';
 $string['name'] = 'Name';
 $string['needregrademessage'] = 'The marking guide definition was changed after this student had been graded. The student can not see this marking guide until you check the marking guide and update the grade.';
 $string['pluginname'] = 'Marking guide';
index f368c14..7c90524 100644 (file)
@@ -57,7 +57,7 @@ class behat_gradingform_guide extends behat_base {
      * @param TableNode $guide
      */
     public function i_define_the_following_marking_guide(TableNode $guide) {
-        $steptableinfo = '| Criterion name | Description for students | Description for markers | Maximum mark |';
+        $steptableinfo = '| Criterion name | Description for students | Description for markers | Maximum score |';
 
         if ($criteria = $guide->getHash()) {
             $addcriterionbutton = $this->find_button(get_string('addcriterion', 'gradingform_guide'));
@@ -92,7 +92,7 @@ class behat_gradingform_guide extends behat_base {
                 $this->set_guide_field_value($criterionroot . '[descriptionmarkers]', $criterion['Description for markers']);
 
                 // Set the field value for the Max score field.
-                $this->set_guide_field_value($criterionroot . '[maxscore]', $criterion['Maximum mark']);
+                $this->set_guide_field_value($criterionroot . '[maxscore]', $criterion['Maximum score']);
             }
         }
     }
index abe994a..f043693 100644 (file)
@@ -29,10 +29,10 @@ Feature: Marking guides can be created and edited
       | Name        | Assignment 1 marking guide     |
       | Description | Marking guide test description |
     And I define the following marking guide:
-      | Criterion name    | Description for students         | Description for markers         | Maximum mark |
-      | Guide criterion A | Guide A description for students | Guide A description for markers | 30           |
-      | Guide criterion B | Guide B description for students | Guide B description for markers | 30           |
-      | Guide criterion C | Guide C description for students | Guide C description for markers | 40           |
+      | Criterion name    | Description for students         | Description for markers         | Maximum score |
+      | Guide criterion A | Guide A description for students | Guide A description for markers | 30            |
+      | Guide criterion B | Guide B description for students | Guide B description for markers | 30            |
+      | Guide criterion C | Guide C description for students | Guide C description for markers | 40            |
     And I define the following frequently used comments:
       | Comment 1 |
       | Comment 2 |
index 1c0da1e..2983874 100644 (file)
@@ -1039,7 +1039,8 @@ function print_grade_page_head($courseid, $active_type, $active_plugin=null,
             if (isset($user)) {
                 $output = $OUTPUT->context_header(
                         array(
-                            'heading' => fullname($user),
+                            'heading' => html_writer::link(new moodle_url('/user/view.php', array('id' => $user->id,
+                                'course' => $courseid)), fullname($user)),
                             'user' => $user,
                             'usercontext' => context_user::instance($user->id)
                         ), 2
index ef92e97..4c3aac7 100644 (file)
@@ -34,7 +34,6 @@
 .dir-rtl.path-grade-report-grader .gradeparent table {
     border-left-width: 0;
     border-right-width: 1px;
-    max-width: initial;
 }
 
 /**
 .path-grade-report-grader .yui3-overlay {
   border: 0;
   background: none;
-  background-color: initial;
+  background-color: inherit;
   min-width: 200px;
 }
 
index d341ffd..71dad25 100644 (file)
@@ -48,7 +48,6 @@ Feature: Using the AJAX grading feature of Grader report to update grades and fe
       | grade_report_showaverages | 0 |
       | grade_report_enableajax | 1 |
 
-
   @javascript
   Scenario: Use the grader report without editing, with AJAX on and quick feedback off
     When the following config values are set as admin:
index bb75576..c185c23 100644 (file)
Binary files a/grade/report/grader/yui/build/moodle-gradereport_grader-gradereporttable/moodle-gradereport_grader-gradereporttable-debug.js and b/grade/report/grader/yui/build/moodle-gradereport_grader-gradereporttable/moodle-gradereport_grader-gradereporttable-debug.js differ
index a151a1b..f81abac 100644 (file)
Binary files a/grade/report/grader/yui/build/moodle-gradereport_grader-gradereporttable/moodle-gradereport_grader-gradereporttable-min.js and b/grade/report/grader/yui/build/moodle-gradereport_grader-gradereporttable/moodle-gradereport_grader-gradereporttable-min.js differ
index 6fcd0dd..ac36953 100644 (file)
Binary files a/grade/report/grader/yui/build/moodle-gradereport_grader-gradereporttable/moodle-gradereport_grader-gradereporttable.js and b/grade/report/grader/yui/build/moodle-gradereport_grader-gradereporttable/moodle-gradereport_grader-gradereporttable.js differ
index 6d496bb..a57aed8 100644 (file)
@@ -259,6 +259,11 @@ FloatingHeaders.prototype = {
             return this;
         }
 
+        if (M.cfg.behatsiterunning) {
+            // If the behat site is running we don't want floating elements.
+            return;
+        }
+
         // Generate floating elements.
         this._setupFloatingUserColumn();
         this._setupFloatingUserHeader();
index 7bde323..075cb11 100644 (file)
@@ -61,7 +61,7 @@ dir-rtl.path-grade-report-singleview div.groupselector select {
 .path-grade-report-singleview .itemnav {
     font-size: small;
     display: inline;
-    margin-bottom: 0.5em;
+    padding-bottom: 0.5em;
 }
 .path-grade-report-singleview itemnav.previtem {
    float:left;
index ca22495..8255c44 100644 (file)
@@ -208,7 +208,9 @@ var removeLoaderImgs = function (elClass, parentId) {
     var parentEl = document.getElementById(parentId);
     if (parentEl) {
         var loader = document.getElementById("loaderImg");
-        parentEl.removeChild(loader);
+        if (loader) {
+            parentEl.removeChild(loader);
+        }
     }
 };
 
index 6a7209d..4bdcf41 100644 (file)
@@ -62,10 +62,10 @@ date_default_timezone_set(@date_default_timezone_get());
 @ini_set('display_errors', '1');
 
 // Check that PHP is of a sufficient version.
-if (version_compare(phpversion(), '5.4.4') < 0) {
+if (version_compare(phpversion(), '5.6.5') < 0) {
     $phpversion = phpversion();
     // do NOT localise - lang strings would not work here and we CAN not move it after installib
-    echo "Moodle 2.7 or later requires at least PHP 5.4.4 (currently using version $phpversion).<br />";
+    echo "Moodle 3.2 or later requires at least PHP 5.6.5 (currently using version $phpversion).<br />";
     echo "Please upgrade your server software or install older Moodle version.";
     die;
 }
diff --git a/install/lang/da_rum/langconfig.php b/install/lang/da_rum/langconfig.php
new file mode 100644 (file)
index 0000000..1c8c74c
--- /dev/null
@@ -0,0 +1,35 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Automatically generated strings for Moodle installer
+ *
+ * Do not edit this file manually! It contains just a subset of strings
+ * needed during the very first steps of installation. This file was
+ * generated automatically by export-installer.php (which is part of AMOS
+ * {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
+ * list of strings defined in /install/stringnames.txt.
+ *
+ * @package   installer
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$string['parentlanguage'] = 'da';
+$string['thisdirection'] = 'ltr';
+$string['thislanguage'] = 'Dansk Rum';
index 7c94740..78f7d06 100644 (file)
@@ -40,3 +40,4 @@ $string['cliunknowoption'] = 'ངོས་འཛིན་འབད་མ་ཚ
 $string['cliyesnoprompt'] = 'y ཟེར་འབྲི་བ་ཅིན་ཨིནཟེརཝ་དང་ n ཟེར་འབྲི་བ་ཅིན་མིན་)';
 $string['environmentrequireinstall'] = 'གཞི་བཙུགས་འབད་དི་ལྕོགས་ཅན་བཟོ་དགོ།';
 $string['environmentrequireversion'] = 'ཐོན་རིམ་  {$a->needed}དགོས་མཁོ་ཡོདཔ་ལས་ ཁྱོད་ཀྱི་ {$a->current}གཡོག་བཀོལ་བའི་བསྒང་ཡོད།';
+$string['upgradekeyset'] = 'ལྡེ་མིག་ཡར་བསྐྱེད་གཏང་། (གཞི་སྒྲིག་མི་འགྱོ་ནི་གི་དོན་ལུ་སྟོངམ་སྦེ་བཞག)';
index 4a638f4..44dcdbc 100644 (file)
@@ -31,6 +31,7 @@
 defined('MOODLE_INTERNAL') || die();
 
 $string['language'] = 'སྐད་ཡིག།';
+$string['moodlelogo'] = 'Moodle ལས་རྟགས།';
 $string['next'] = 'ཤུལ་མམ';
 $string['previous'] = 'ཧེ་མམ';
 $string['reload'] = 'ཡང་བསྐྱར་མངོན་གསལ་འབད';
index 2762ccc..5181c42 100644 (file)
@@ -31,7 +31,7 @@
 defined('MOODLE_INTERNAL') || die();
 
 $string['language'] = 'Idioma';
-$string['moodlelogo'] = 'Logotipo Moodle';
+$string['moodlelogo'] = 'Logótipo Moodle';
 $string['next'] = 'Seguinte';
 $string['previous'] = 'Anterior';
 $string['reload'] = 'Recarregar';
index e723e1b..42a202e 100644 (file)
@@ -64,5 +64,6 @@ $string['phpversionhelp'] = '<p>Moodle necesită o versiune PHP de cel puțin  4
 <p>Trebuie să upgradați PHP sau să îl mutați pe o gazdă cu o nouă versiune de PHP!<br />
 (În cazul 5.0.x puteți, de asemenea, să downgradați la versiunea 4.4.x)</p>';
 $string['welcomep10'] = '{$a->installername} ({$a->installerversion})';
+$string['welcomep20'] = 'Vedeți această pagină deoarece ați instalat și lansat cu succes pachetul  <strong>{$a->packname} {$a->packversion}</strong> în computerul dumneavoastră. Felicitări!';
 $string['welcomep40'] = 'Pachetul include și <strong>Moodle {$a->moodlerelease} ({$a->moodleversion})</strong>.';
 $string['wwwroot'] = 'Adresă Web';
index c09b45d..855b16e 100644 (file)
@@ -42,3 +42,4 @@ $string['cliunknowoption'] = 'Нераспознанные параметры:
 $string['cliyesnoprompt'] = 'Введите y (обозначает Да) или n (обозначает Нет)';
 $string['environmentrequireinstall'] = 'необходимо установить и включить';
 $string['environmentrequireversion'] = 'требуется версия {$a->needed}, у Вас используется версия {$a->current}';
+$string['upgradekeyset'] = 'Ключ обновления (оставьте пустым, если не хотите его задавать)';
index 599a475..5dffe08 100644 (file)
@@ -31,6 +31,7 @@
 defined('MOODLE_INTERNAL') || die();
 
 $string['language'] = 'Язык';
+$string['moodlelogo'] = 'Логотип Moodle';
 $string['next'] = 'Далее';
 $s